¿Por qué los valores mutables en Python Enums son el mismo objeto?
enum java (4)
Al experimentar con diferentes tipos de valores para los miembros de Enum
, descubrí algún comportamiento extraño cuando los valores son mutables.
Si defino los valores de un Enum
como listas diferentes, los miembros aún se comportarán de manera similar a cuando los valores Enum
son tipos inmutables típicos como str
o int
, aunque puedo cambiar los valores de los miembros en su lugar para que los valores de los dos Enum
miembros son los mismos:
>>> 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
Sin embargo, si defino que los valores sean listas idénticas, el valor de cada miembro parece ser el mismo objeto y, por lo tanto, cualquier mutación del valor de un miembro afecta a todos los miembros:
>>> 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
¿Por qué Enum
comporta de esta manera? ¿Es el comportamiento previsto o es un error?
NOTA: No estoy planeando usar Enums de esta manera, simplemente estaba experimentando con el uso de valores no estándar para los miembros de Enum
De los docs :
Dados dos miembros A y B con el mismo valor (y A definido primero), B es un alias a A. La búsqueda por valor del valor de A y B devolverá A. La búsqueda por nombre de B también devolverá 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>
Esto funciona por igualdad, incluso cuando los valores son mutables. Como definió valores iguales para black
y blue
, con black
primero, blue
es un alias para black
.
La clase Python 3 Enum no impone exclusividad a menos que se lo digas específicamente a través del decorador único
Véase también docs . Ya que el blue
es idéntico al black
, solo se convierte en un alias para el black
.
Para complementar la respuesta de @ user2357112 , eche un vistazo en EnumMeta
, la metaclase para todas las clases de Enum
; obtiene un vistazo a cada definición de clase que tiene su tipo y recibe un cambio para alterarlo.
Específicamente, se encarga de volver a asignar miembros con el mismo valor en su método __new__
mediante una tarea simple:
# 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
No opté por revisar los documentos y en su lugar busqué en la fuente. Lección para tomar: Siempre revise los documentos, y si se ExplanationNotFound
; compruebe la fuente :-)
De la documentación de Python para Enums :
De forma predeterminada, las enumeraciones permiten varios nombres como alias para el mismo valor . Cuando no se desea este comportamiento, se puede usar el siguiente decorador para garantizar que cada valor se use solo una vez en la enumeración ...
Esto significa que el blue
es un alias para el black
. Cuando cualquiera de los dos cambia, el otro también debe hacerlo.
Sin embargo, puede forzar a Python a hacer que cada valor de enumeración sea único, utilizando el decorador enum.unique
. También desde la documentación:
>>> 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