17

How to define an attribute in a Python 3 enum class that is NOT an enum value?

class Color(Enum):
    red = 0
    blue = 1
    violet = 2
    foo = 'this is a regular attribute'
    bar = 55  # this is also a regular attribute

But this seems to fail for me. It seems that Color tries to include foo and bar as part of its enum values.

EDIT: Lest you think I'm not using Enum in a way that's intended... For example, take the official Python documentation's example enum class Planet (docs.python.org/3/library/enum.html#planet). Note that they define the gravitational constant G within the surface_gravity() method. But this is weird code. A normal programmer would say, set that constant G once, outside the function. But if I try to move G out (but not to global scope, just class scope), then I run into the issue I'm asking about here.

eng
  • 201
  • 2
  • 6
  • 1
    Why do you need this? You can't do it like that, no. – Martijn Pieters Oct 08 '15 at 06:43
  • There is no built-in facility that regards some attributes as enums and others as 'regular'. There is no difference between `foo` and `bar` in your enum as far as Python is concerned. Just because you used a different type object for the value makes no difference. – Martijn Pieters Oct 08 '15 at 06:45
  • 1
    Well, because one of the reasons for the existence of these fancy Enum classes is that they can be regular classes, you know, with methods and such, and eventually, I get to a point where I need a class-level constant! – eng Oct 08 '15 at 06:45
  • Class-level 'constants' are always members of the enumeration if defined in the class body; Python doesn't have 'constants' anyway. I'd stick to a global instead. – Martijn Pieters Oct 08 '15 at 06:49
  • It is possible to have class-level constants in an `Enum` -- see [this answer](http://stackoverflow.com/a/18035135/208880). There is no protection against reassigning the "constant" value, though -- just like in regular classes. – Ethan Furman Mar 09 '16 at 05:19
  • 1
    @EthanFurman I don’t really understand why you feel the need to go to a *closed* question 5 months after closing it, linking explicitly to *your* answer again (which is also the accepted answer on the close target), *and downvoting* the two answers to this closed question when those answers may be a bit complicated but nevertheless both provide a *working* solution for the problem. Are you that overconfident that *your* solution is the *only* acceptable solution for this requirement? – poke Mar 09 '16 at 11:24
  • 1
    @poke: I'm reviewing all the [enums] [python] answers, and up/down-voting where appropriate. I don't agree with your characterization of the `Enum` data type in this instance (you have up-votes from me in other instances). Had somebody else written my solution I would have acted the same to these answers/comments. – Ethan Furman Mar 09 '16 at 14:56

2 Answers2

12

The point of the Enum type is to define enum values, so non-enum values are theoretically out of scope for this type. For constants, you should consider moving them out of the type anyway: They are likely not directly related to the enum values (but rather some logic that builds on those), and also should be a mutable property of the type. Usually, you would just create a constant at module level.

If you really need something on the type, then you could add it as a class method:

class Color(Enum):
    red = 0
    blue = 1
    violet = 2
    bar = 55

    @classmethod
    def foo (cls):
        return 'this is a not really an attribute…'

And using the classproperty descriptor from this answer, you can also turn this into a property at class level which you can access as if it was a normal attribute:

class Color(enum.Enum):
    red = 0
    blue = 1
    violet = 2
    bar = 55

    @classproperty
    def foo (cls):
        return 'this is a almost a real attribute'
>>> Color.foo
'this is a almost a real attribute'
>>> list(Color)
[<Color.red: 0>, <Color.blue: 1>, <Color.violet: 2>, <Color.bar: 55>]
Community
  • 1
  • 1
poke
  • 369,085
  • 72
  • 557
  • 602
  • 1
    @EthanFurman well, yes. Work days can become quite busy very quickly. I don’t remember the context of that quote any more, and it’s really difficult to know what I was thinking *5* months later… but I guess what I was trying to say is that when you have a member out in the open on the type object, the public interface just begs to access and modify it. That’s not really a good idea for constants. Constants should be somewhere safe, or at least clearly marked as constants. – poke Mar 09 '16 at 22:23
  • Ah, that makes sense. Thanks. – Ethan Furman Mar 09 '16 at 22:24
4

When building an enum.Enum class, all regular attributes become members of the enumeration. A different type of value does not make a difference.

By regular attributes I mean all objects that are not descriptors (like functions are) and excluded names (using single underscore names, see the Allowed members and attributes of enumerations section).

If you need additional attributes on the final enum.Enum object, add attributes afterwards:

class Color(Enum):
    red = 0
    blue = 1
    violet = 2

Color.foo = 'this is a regular attribute'
Color.bar = 55

Demo:

>>> from enum import Enum
>>> class Color(Enum):
...     red = 0
...     blue = 1
...     violet = 2
... 
>>> Color.foo = 'this is a regular attribute'
>>> Color.bar = 55
>>> Color.foo
'this is a regular attribute'
>>> Color.bar
55
>>> Color.red
<Color.red: 0>
>>> list(Color)
[<Color.red: 0>, <Color.blue: 1>, <Color.violet: 2>]
Michael Clerx
  • 2,928
  • 2
  • 33
  • 47
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • The `Enum` metaclass does support @eng's use-case via the descriptor protocol (in other words, Python supports it, I didn't have to do anything extra). – Ethan Furman Mar 09 '16 at 05:10
  • @EthanFurman: yes you did, you had to create a descriptor, which in my book is certainly doing something extra. Which is a nice and clever trick, but that doesn't make my technically incorrect. Was my answer really that unhelpful to deserve a downvote? – Martijn Pieters Mar 09 '16 at 11:17
  • Ah, I meant I didn't have to do anything extra when creating the metaclass to support this use-case. Due to the ugly hack of assigning attributes after class creation and the wrong information in the comments, I downvoted. My hope was that an overall -1 score would warn future seekers not to use these answers. – Ethan Furman Mar 09 '16 at 14:45
  • @EthanFurman: We can easily clean up the comments; just flag those you think should go. They are not part of the answer. You mention assigning attributes after class creation as a solution in your own answer. – Martijn Pieters Mar 09 '16 at 14:55
  • 1
    Yes, I did mention assigning attributes after class creation in my answer; I also mentioned why it is not a good solution. – Ethan Furman Mar 09 '16 at 15:00