为什么Python Enums允许可变值?

8
这篇文章是为什么Python Enums中的可变值是同一对象的跟进内容
如果一个Enum的值是可变的(例如list等),那么这些值可以随时更改。我认为,如果按值检索Enum成员,这会造成一些问题,特别是如果有人无意中更改了他查找的Enum的值:
>>> from enum import Enum
>>> class Color(Enum):
        black = [1,2]
        blue = [1,2,3]

>>> val_1 = [1,2]
>>> val_2 = [1,2,3]

>>> Color(val_1)
<Color.black: [1, 2]>

>>> Color(val_2)
<Color.blue: [1, 2, 3]>

>>> my_color = Color(val_1)
>>> my_color.value.append(3)

>>> Color(val_2)
<Color.black: [1, 2, 3]>

>>> Color(val_1)
Traceback (most recent call last):
  ...
ValueError: [1, 2] is not a valid Color

我认为,根据Python的常规惯用法,这是可以接受的,其含义是用户可以使用可变类型作为他们的Enum值,但是需要了解他们可能会遇到的问题。
然而,这引出了第二个问题-由于您可以通过值查找Enum成员,并且该值可以是可变的,因此必须使用除哈希表/ dict之外的其他方法进行查找,因为可变内容不能在这种dict中作为键。
限制Enum值仅为不可变类型以便可以使用dict实现按值查找是否更有效(尽管灵活性较差)?
2个回答

6

看起来,我第二个问题的答案就在enum.py的源代码中显而易见。

每个Enum实例都包含一个dict,其中存储了可哈希(即不可变)数据类型的value->member键值对。当您通过值查找Enum时,它会尝试从该dict中检索成员。如果该值不是可哈希的,则会强制使用所有现有的Enum值进行相等性比较,如果找到匹配项,则返回该成员。相关的代码在enum.py的468-476行中:

try:
    if value in cls._value2member_map_:
        return cls._value2member_map_[value]
except TypeError:
    # not there, now do long search -- O(n) behavior
    for member in cls._member_map_.values():
        if member._value_ == value:
            return member
raise ValueError("%r is not a valid %s" % (value, cls.__name__))

因此,看起来enum.py的设计者希望在按值获取Enum时具有快速查找功能,但仍希望为Enum值提供可变值的灵活性(尽管我仍然想不出为什么有人首先想要那样做)。

这是一个可变性的用例:假设程序具有包含枚举的多个退出代码,并且每个代码都有自己的退出消息(例如,CODE = [123, "Exit #123"])。其中一个退出情况是当用户调用脚本不正确时,您需要显示参数参考。问题在于,引用封装在 ArgumentParser 对象中。因此,首先初始化枚举,然后执行多个 add_argument(),然后执行以下操作:ExitCode.HELP.value[1] += '\n' + args.format_help()。Voilà! - hidefromkgb
据我理解你的例子,它可以使用元组来实现。 - Graham
一个使用案例可能是:class Foo(Enum): NO_REPLY = {'name': 'Team Bar', 'email': 'no-reply@bar.com'} SUPPORT = {'name': 'Team Bar', 'email': 'support@bar.com'}但我觉得这不太好,所以我改成了这样:class Foo(Enum): NO_REPLY SUPPORT 然后保留一个字典:addresses = {Foo.NO_REPLY: {'name': 'Team Bar', 'email': 'no-reply@bar.com'}, Foo.SUPPORT: {'name': 'Team Bar', 'email': 'support@bar.com'}} - Vikas Prasad
转念一想,我认为我可以使用enumnamedtuple更加简洁地处理我的用例:Address = namedtuple('Address', ['name', 'email']) class Foo(Enum): NO_REPLY = Address('Team Bar', 'no-reply@bar.com') SUPPORT = Address('Team Bar', 'support@bar.com') - Vikas Prasad

2
值得强调的是,根据文档,枚举值可以是任何东西

请注意,枚举成员值可以是任何东西:int、str等等。如果确切的值不重要,您可以使用auto实例,适当的值将为您选择。如果您混合使用auto和其他值,则必须小心。 https://docs.python.org/3/library/enum.html#creating-an-enum

这与其他语言中的枚举实体相比,是一个相当大的改变。但是这样做可能会产生一些有趣的可能性。我喜欢将字符串作为值变量,在源代码中使用友好的枚举名称,而枚举值则可以用于表示前端代码或控制台应用程序帮助文本等。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接