3

I want to simplify the following class so that I don't need to type the value for every element and instead use the name of the enum member. This is similar to the AutoNumber example in the documentation.

class Screen(Enum):
    MAIN = 'main'
    OPTIONS = 'options'
    GAME = 'game'
    SCORE = 'score'

Instead of the above, I'd like to do the following, so that e.g. Screen.MAIN is 'main':

class Screen(AutoNameValueEnum):
    MAIN = ()
    OPTIONS = ()
    GAME = ()
    SCORE = ()

I tried modifying the linked example, but I couldn't figure out how to get the name of the enum member.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
Joschua
  • 5,816
  • 5
  • 33
  • 44
  • I'm afraid it's impossible to modify that example for your case. `DuplicateFreeEnum`, on the other hand, looks like a more promising start. – Oleh Prypin Jun 14 '14 at 19:47
  • But wait... Why do you want this? You can easily get the name from the items. – Oleh Prypin Jun 14 '14 at 19:49
  • You're right, but I think it would be cleaner to use than e.g. `Screen.MAIN.name.lower()` or adding another attribute with that value. (If there's really no way, I'd be fine with it though.) I tried modifying `DuplicateFreeEnum`, but it does not allow to change the value. It raises `AttributeError: can't set attribute` – Joschua Jun 14 '14 at 19:59
  • @EthanFurman Could you post an answer where you are modifying the AutoNumber example with `__new__`? I couldn't figure out how to get the enum member name. – Joschua Jun 18 '14 at 15:14
  • It isn't possible, which is why I said you can't do it with the `class` method (in my updated answer). Apologies for my previous comment being incomplete. – Ethan Furman Jun 18 '14 at 15:56
  • @EthanFurman Why is my question marked as a duplicate when I asked it earlier? Just wondering. – Joschua Mar 14 '16 at 11:44
  • 1
    The accepted answer on this question breaks `Enum`s. – Ethan Furman Mar 14 '16 at 13:57

2 Answers2

4

In an Enum, the members are also Enums themselves. The example from the documentation uses the __new__ method of the members to assign a value. You can't use that method, because the new member does not know how it is called within its owning class. Rather, use the initializer. Here's an example:

>>> class AutoNameValueEnum(enum.Enum):
...    def __init__(self):
...        self._value_ = self._name_.lower()
...
>>> class Color(AutoNameValueEnum):
...    RED = ()
...
>>> Color.RED
<Color.RED: 'red'>

Update:

Note that with this solution, value lookups are no longer possible. This is because the enum metaclass stores values before calling the initializer. This will effectively cut you out from pickling your enum and might also break things in some other places. An alternative (which breaks equivalence to similar strings, i.e. a is "foo", for values, but nothing else) is to use an unhashable type for the values. The enum class will then do a linear search rather than try to lookup keys for values through a map.

class UnhashableString(str):
    def __hash__(self):
        raise TypeError

class AutoNameValueEnum(enum.Enum):
    def __init__(self):
        self._value_ = UnhashableString(self._name_.lower())

class Color(AutoNameValueEnum):
    RED = ()

Is the updated example for this alternative. All in all, I think that using the functional interface suggested here is the cleanest solution, because you can be sure that nothing breaks when you use it.

Community
  • 1
  • 1
Phillip
  • 13,448
  • 29
  • 41
  • 3
    What the hell is `_value_` and `_name_` and why isn't it documented? – Oleh Prypin Jun 14 '14 at 20:07
  • 1
    @OlehPrypin: A look at the source indicates that `_name_` and `_value_` are the attributes that back the `name` and `value` descriptors: http://hg.python.org/cpython/file/3.4/Lib/enum.py#l499 – user2357112 Jun 14 '14 at 20:14
  • 1
    They are the internal class members of enum members which the implementation (in the `enum` module) uses when the actual `name` and `value` properties are accessed. As for the missing documentation, I don't know why exactly they are not properly documented, but the `AutoNumber` example also uses the `_value_` property, so despite the [leading underscore in the name](http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python) they seem to be intended for use in subclasses.. – Phillip Jun 14 '14 at 20:17
  • Changing the `_value_` in `__init__` is not the proper way, and breaks `Enum`. These types of changes should be done in `__new__` -- that is what `__new__` is for, as the AutoNumber example shows. – Ethan Furman Jun 16 '14 at 18:56
  • I feared that, but couldn't find anything that actually breaks. Could you give an example to show the problems? – Phillip Jun 17 '14 at 07:14
  • Found it myself, for some reason in the source, the MetaClass first stores a copy of the value and then calls `__init__`. (It'd be interesting to know why it was implemented this way?!) This has the effect that the `_value2member_map_` has the wrong value, which makes value to key lookups fail. They are p.e. required in unpickling. To change this behaviour you'd have to override the meta class; I concur that the functional API provides a simpler solution here. – Phillip Jun 17 '14 at 19:24
  • Another solution is to use an unhashable type for the values. The enum implementation will in this case use a linear search through the class members to do value lookups. I'll update my answer accordingly, but still think that the other solution is simpler. – Phillip Jun 17 '14 at 19:51
  • When addressing comments to someone who is not the owner of the post you are commenting on (so me on your answer, or me on the question) make sure and address it with @UserName and then that person will be notified that a comment was directed to them. I would have answered your question had I seen it (only stumbled on it just now by accident). – Ethan Furman Jun 17 '14 at 21:42
  • @Phillip Sadly, even with your latter version, lookup doesn't work. `Color(UnhashableString('red'))` works, but `Color('red')` gives `ValueError`. – Joschua Jun 20 '14 at 10:15
  • I meant, it does work, but only by using UnhashableString. – Joschua Jun 20 '14 at 10:35
  • @Joschua Yes. The point is that *automated* lookups will work, because they will use the previously serialized value, which is a `UnhashableString`. If you really want to do manual lookups by a string value there is no alternative to either not using an enum or preparing a list of names and values before creating the class, as Ethan Furman suggested. – Phillip Jun 20 '14 at 14:11
4

Update

Python 3.6 and aenum 2.0 have a new method that can be overridden to allow custom values for enum members making this process quite easy:

def _generate_next_value_(name, start, count, last_values):
    return name
  • name is the name of the member
  • start is the start value for the enumeration (defaults to 1)
  • count is the number of members so far
  • last_values is a list of the member values so far

You cannot acheive this using the class method*, but you can using the Functional API:

Color = enum.Enum(
    'Color',
    ((name, name.lower()) for name in 'BLACK RED GREEN BLUE'.split()),
    )

*Okay, like most things in Python if you try hard enough you can, but why go to all that extra work?

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237