This is somewhat of a follow on to Why are mutable values in Python Enums the same object?.
If the values of an Enum
are mutable (e.g. list
s, etc.), those values can be changed at any time. I think this poses something of an issue if Enum
members are retrieved by value, especially if someone inadvertently changes the value of an Enum
he looks up:
>>> from enum import Enum
>>> class Color(Enum):
black = [1,2]
blue = [1,2,3]
>>> val_1 = [1,2]
>>> val_2 = [1,2,3]
>>> Color(val_1)
<Color.black: [1, 2]>
>>> Color(val_2)
<Color.blue: [1, 2, 3]>
>>> my_color = Color(val_1)
>>> my_color.value.append(3)
>>> Color(val_2)
<Color.black: [1, 2, 3]>
>>> Color(val_1)
Traceback (most recent call last):
...
ValueError: [1, 2] is not a valid Color
I think given normal Python idioms this is okay, with the implication being that users can use mutables as their Enum
values, but just to understand the can of worms they might be opening.
However this brings up a second issue - since you can look up an Enum
memeber by value, and the value can be mutable, it must be doing the lookup by a means other than a hashmap/dict
, since the mutable cannot be a key
in such a dict
.
Wouldn't it be more efficient (although, granted, less flexible) to limit Enum
values to only immutable types so that lookup-by-value could be implemented with a dict
?
Enumerations are immutable It means you cannot add or remove members once an enumeration is defined. And you also cannot change the member values.
Enums can't have multiple value per name.
Every enumerator or variable name in the scope must be unique. However, the values can be duplicated. In an unscoped enum, the scope is the surrounding scope; in a scoped enum, the scope is the enum-list itself.
Python's enum module provides a convenient function called auto() that allows you to set automatic values for your enum members. This function's default behavior is to assign consecutive integer values to members.
It appears the answer to my second question was hiding in plain sight in the soure code for enum.py
.
Each Enum
does contain a dict
of value->member
pairs for hashable (i.e. immutable) values, and when you look up an Enum
by value, it attempts to retrieve the member from that dict
. If the value is not hashable, it then brute-force compares for equality against all existing Enum
values, returning the member if finds a match. The relevant code is in lines 468-476 in enum.py
:
try:
if value in cls._value2member_map_:
return cls._value2member_map_[value]
except TypeError:
# not there, now do long search -- O(n) behavior
for member in cls._member_map_.values():
if member._value_ == value:
return member
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
So it appears as though the designers of enum.py
wanted to have a quick lookup when getting Enum
s by value, but still wanted to give the flexibility of having mutable values for Enum
values (even though I still can't think of a reason why someone would want that in the first place).
It's worth highlighting that enum values can be anything according to the documentation.
Note Enum member values Member values can be anything: int, str, etc.. If the exact value is unimportant you may use auto instances and an appropriate value will be chosen for you. Care must be taken if you mix auto with other values. https://docs.python.org/3/library/enum.html#creating-an-enum
It's a pretty big departure from other enum entities in other languages. The allowance should make for some interesting possibilities however. I like the string as value variant where the source code friendly enum name is used in source while the enum value can serve for presentation purposes as in front-end code or console app help text or something.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With