Reputation: 5609
While experimenting with different value types for Enum
members, I discovered some odd behavior when the values are mutable.
If I define the values of an Enum
as different lists, the members still behave similarly to when the Enum
values are typical immutable types like str
or int
, even though I can change the values of the members in place so that the values of the two Enum
members are the same:
>>> class Color(enum.Enum):
black = [1,2]
blue = [1,2,3]
>>> Color.blue is Color.black
False
>>> Color.black == Color.blue
False
>>> Color.black.value.append(3)
>>> Color.black
<Color.black: [1, 2, 3]>
>>> Color.blue
<Color.blue: [1, 2, 3]>
>>> Color.blue == Color.black
False
>>> Color.black.value == Color.blue.value
True
However, if I define the values to be identical lists, each member's value seems to be the same object, and thus any mutation of one member's value affects all members:
>>> class Color(enum.Enum):
black = [1,2,3]
blue = [1,2,3]
>>> Color.blue is Color.black
True
>>> Color.black == Color.blue
True
>>> Color.black.value.append(4)
>>> Color.black
<Color.black: [1, 2, 3, 4]>
>>> Color.blue
<Color.black: [1, 2, 3, 4]>
>>> Color.blue == Color.black
True
Why does Enum
behave this way? Is it the intended behavior or is it a bug?
NOTE: I'm not planning on actually using Enums this way, I was simply experimenting with using non-standard values for Enum members
Upvotes: 19
Views: 1944
Reputation: 160377
To complement @user2357112's answer, take a look in EnumMeta
, the metaclass for all Enum
classes; it gets a peek at every class definition that has its type and gets a change to alter it.
Specifically, it takes care to re-assign members with the same value in its __new__
method via simple assignment:
# If another member with the same value was already defined, the
# new member becomes an alias to the existing one.
for name, canonical_member in enum_class._member_map_.items():
if canonical_member._value_ == enum_member._value_:
enum_member = canonical_member
break
I didn't opt to check the docs and instead looked in the source. Lesson to take: Always check the docs, and if ExplanationNotFound
is raised; check the source :-)
Upvotes: 5
Reputation: 22953
From the Python documentation for Enums:
By default, enumerations allow multiple names as aliases for the same value. When this behavior isn’t desired, the following decorator can be used to ensure each value is used only once in the enumeration....
This means that blue
is an alias for black
. When either one changes, the other must as well.
You can however, force Python to make each enum value unique, by using the enum.unique
decorator. Also from the docs:
>>> from enum import Enum, unique >>> @unique ... class Mistake(Enum): ... one = 1 ... two = 2 ... three = 3 ... four = 3 ... Traceback (most recent call last): ... ValueError: duplicate values found in <enum 'Mistake'>: four -> three
Upvotes: 4
Reputation: 7952
Python 3 Enum class doesn't enforce uniqueness unless you specifically tell it to via the unique decorator
See also duplicate values. since blue
is identical to black
, it just becomes an alias for black
.
Upvotes: 3
Reputation: 280182
From the docs:
Given two members A and B with the same value (and A defined first), B is an alias to A. By-value lookup of the value of A and B will return A. By-name lookup of B will also return A:
>>> class Shape(Enum): ... square = 2 ... diamond = 1 ... circle = 3 ... alias_for_square = 2 ... >>> Shape.square <Shape.square: 2> >>> Shape.alias_for_square <Shape.square: 2> >>> Shape(2) <Shape.square: 2>
This operates by equality, even when the values are mutable. Since you defined equal values for black
and blue
, with black
first, blue
is an alias for black
.
Upvotes: 12