Почему переменные значения в Python перечисляет один и тот же объект?
Во время экспериментов с разными типами значений для членов Enum
я обнаружил некоторое нечетное поведение, когда значения изменяемы.
Если я определяю значения Enum
как разные списки, члены по-прежнему ведут себя аналогично тому, когда значения Enum
являются типичными неизменяемыми типами, такими как str
или int
, хотя я могу изменить значения члены на месте, чтобы значения двух членов Enum
были одинаковыми:
>>> 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
Однако, если я определяю значения как идентичные списки, каждое значение элемента похоже будет тем же самым объектом, и, таким образом, любая мутация одного значения элемента влияет на всех членов:
>>> 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
Почему Enum
ведет себя так? Является ли это предполагаемым поведением или это ошибка?
ПРИМЕЧАНИЕ: Я не планирую фактически использовать Enums таким образом, я просто экспериментировал с использованием нестандартных значений для членов Enum
Ответы
Ответ 1
Из docs:
Учитывая два члена A и B с тем же значением (и A, определенным вначале), B является псевдонимом A. Поиск по значению значений A и B будет возвращать A. Поиск по имени B также будет 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>
Это работает по принципу равенства, даже если значения изменяемы. Поскольку вы определили равные значения для black
и blue
, сначала black
, blue
является псевдонимом для black
.
Ответ 2
Чтобы дополнить @user2357112 ответ, загляните в EnumMeta
, метакласс для всех классов Enum
; он получает быстрый взгляд на каждое определение класса, которое имеет свой тип и получает изменения, чтобы изменить его.
В частности, позаботится переназначить членов с тем же значением в своем методе __new__
через простое назначение:
# 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
Я не хотел проверять документы и вместо этого смотрел исходный код. Занятие: Всегда проверяйте документы, и если ExplanationNotFound
поднят; проверьте источник: -)
Ответ 3
Класс Python 3 Enum не обеспечивает уникальность, если вы специально не скажете его через уникальный декоратор
См. также повторяющиеся значения. поскольку blue
идентичен black
, он просто становится псевдонимом для black
.
Ответ 4
Из документации Python для Enums:
По умолчанию перечисления позволяют использовать несколько имен в качестве псевдонимов для одного и того же значения. Когда это поведение не требуется, следующий декоратор может быть использован для обеспечения того, чтобы каждое значение использовалось только один раз в перечислении....
Это означает, что blue
является псевдонимом для black
. Когда один из них меняется, другой должен также.
Однако вы можете заставить Python сделать каждое значение перечисления уникальным, используя декоратор enum.unique
. Также из документов:
>>> 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