为什么Python中的可变值枚举相同的对象?

时间:2023-01-26 13:19:42

While experimenting with different value types for Enum members, I discovered some odd behavior when the values are mutable.

在尝试使用Enum成员的不同值类型时,我发现了一些奇怪的行为,当值是可变的时。

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:

如果我将Enum的值定义为不同的列表,那么成员的行为仍然类似于Enum值是典型的不可变类型(如str或int),即使我可以更改成员的值以使两者的值相同枚举成员是相同的:

>>> 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?

为什么Enum表现得这样?这是预期的行为还是一个错误?

NOTE: I'm not planning on actually using Enums this way, I was simply experimenting with using non-standard values for Enum members

注意:我不打算以这种方式实际使用Enums,我只是在尝试使用Enum成员的非标准值

4 个解决方案

#1


10  

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:

给定两个成员A和B具有相同的值(并且A定义为第一个),B是A的别名.A和B的值的值按字母顺序查找将返回A.B的名字查找也将返回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.

即使值是可变的,这也是平等的。由于您为黑色和蓝色定义了相等的值,首先是黑色,蓝色是黑色的别名。

#2


5  

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.

要补充@ user2357112的答案,请查看EnumMeta,它是所有Enum类的元类;它可以查看具有类型的每个类定义,并进行更改以更改它。

Specifically, it takes care to re-assign members with the same value in its __new__ method via simple assignment:

具体来说,它通过简单的赋值在__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

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 :-)

我没有选择检查文档而是查看源代码。课程:始终检查文档,以及是否引发了InterpreNotFound;检查来源:-)

#3


3  

Python 3 Enum class doesn't enforce uniqueness unless you specifically tell it to via the unique decorator

Python 3 Enum类不强制唯一性,除非您通过独特的装饰器明确告诉它

See also duplicate values. since blue is identical to black, it just becomes an alias for black.

另请参见重复值。因为蓝色与黑色相同,所以它只是黑色的别名。

#4


3  

From the Python documentation for Enums:

从Enums的Python文档:

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:

但是,您可以通过使用enum.unique装饰器强制Python使每个枚举值唯一。同样来自文档:

>>> 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

#1


10  

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:

给定两个成员A和B具有相同的值(并且A定义为第一个),B是A的别名.A和B的值的值按字母顺序查找将返回A.B的名字查找也将返回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.

即使值是可变的,这也是平等的。由于您为黑色和蓝色定义了相等的值,首先是黑色,蓝色是黑色的别名。

#2


5  

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.

要补充@ user2357112的答案,请查看EnumMeta,它是所有Enum类的元类;它可以查看具有类型的每个类定义,并进行更改以更改它。

Specifically, it takes care to re-assign members with the same value in its __new__ method via simple assignment:

具体来说,它通过简单的赋值在__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

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 :-)

我没有选择检查文档而是查看源代码。课程:始终检查文档,以及是否引发了InterpreNotFound;检查来源:-)

#3


3  

Python 3 Enum class doesn't enforce uniqueness unless you specifically tell it to via the unique decorator

Python 3 Enum类不强制唯一性,除非您通过独特的装饰器明确告诉它

See also duplicate values. since blue is identical to black, it just becomes an alias for black.

另请参见重复值。因为蓝色与黑色相同,所以它只是黑色的别名。

#4


3  

From the Python documentation for Enums:

从Enums的Python文档:

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:

但是,您可以通过使用enum.unique装饰器强制Python使每个枚举值唯一。同样来自文档:

>>> 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