【问题标题】:Why are mutable values in Python Enums the same object?为什么 Python 枚举中的可变值是同一个对象?
【发布时间】:2016-11-16 19:59:24
【问题描述】:

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

如果我将Enum 的值定义为不同的列表,则成员的行为仍然类似于Enum 值是典型的不可变类型(如strint)时的行为,即使我可以更改成员到位,以便两个 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 会这样?这是预期的行为还是错误?

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

【问题讨论】:

    标签: python python-3.x enums identity


    【解决方案1】:

    来自docs

    给定两个成员 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>
    

    即使值是可变的,这也是通过相等来操作的。由于您为blackblue 定义了相等的值,首先是black,所以blueblack 的别名。

    【讨论】:

    • 当我将&lt;Color.black: [1, 2, 3, 4]&gt; 作为repr for Color.blue 时,在第二个示例中应该很明显......谢谢!
    【解决方案2】:

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

    具体来说,通过简单的赋值注意re-assign members with the same value in its __new__ method

        # 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】:

      From the Python documentation for Enums:

      默认情况下,枚举允许多个名称作为同一值的别名。当不需要这种行为时,可以使用以下装饰器来确保每个值在枚举中只使用一次......

      这意味着blueblack 的别名。当其中任何一个发生变化时,另一个也必须发生变化。

      但是,您可以通过使用 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
      

      【讨论】:

        【解决方案4】:

        除非您通过 unique decorator 明确告诉它,否则 Python 3 Enum 类不会强制执行唯一性

        另见duplicate values。因为blueblack 相同,所以它只是成为black 的别名。

        【讨论】:

        • 有用的评论,但有用的答案,IMO。
        猜你喜欢
        • 2017-04-01
        • 1970-01-01
        • 2011-04-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多