使用 __prepare__ 作为枚举……有什么问题?

Using __prepare__ for an Enum ... what's the catch?

枚举元类的 Python 的 enum.Enum requires values to be provided, when in the most basic use case for an enum we don't actually care about names and values. We only care about the sentinels themselves. After reading a recently, I realised it is possible to use the __prepare__ 方法的声明式用法来获得这种声明:

class Color(Enum):
    red
    blue
    green

让事情变得如此枯燥的实现实际上相当简单:

from collections import defaultdict

class EnumMeta(type):
    @classmethod
    def __prepare__(meta, name, bases):
        return defaultdict(object)

    def __new__(cls, name, bases, classdict):
        classdict.default_factory = None
        return type.__new__(cls, name, bases, classdict)

class Enum(metaclass=EnumMeta):
    pass

在Python 3.6中,提供了enum.auto to help with that issue of omitting values,但是接口还是很奇怪——需要为每个成员指定auto()值,并且继承自修复 __repr__ 的不同基数:

class Color(NoValue):
    red = auto()
    blue = auto()
    green = auto()

知道为标准库选择的实现已经投入了大量的工时和细心,一定有一些原因可以解释为什么前面演示的可以说是更 Pythonic 的声明性枚举版本没有好好工作。

我的问题是,所提议方法的问题和失败模式是什么,为什么这个(或类似的东西)被决定反对 - auto 特性包含在 Python 3.6 中相反?

defaultdict 作为 Enum 的命名空间有几个陷阱:

  • 除其他枚举外无法访问任何内容members/methods
  • 拼写错误创建新成员
  • 失去对 _EnumDict 命名空间的保护:
    • 覆盖成员
    • 覆盖方法
    • 较新的_generate方法

最重要的是:

  • 不行

为什么它不起作用? __prepare__ 不仅可以在命名空间字典上设置属性,命名空间字典本身也可以 - 而 _EnumDict 可以:_member_names,应该是成员的所有属性的列表。

然而,声明一个没有值的名字的目标并非不可能——aenum1 包允许它有一些安全措施:

  • 神奇的自动行为仅在定义成员时出现(一旦定义了普通方法,它就会关闭)
  • propertyclassmethodstaticmethod 默认被排除,但可以包含它们 and/or 排除其他全局名称

尽管如此,这种行为对于 stdlib 来说被认为太神奇了,所以如果你想要它,连同其他一些 enhancements/improvements2,你将不得不使用aenum.

一个例子:

from aenum import AutoEnum

class Color(AutoEnum):
    red
    green
    blue

不过 __repr__ 仍然显示创建的值。

--

1 披露:我是 Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) 库的作者。

2 NamedConstant(就像它说的那样;),NamedTuple(基于元类,默认值等),加上一些内置枚举:

  • MultiValueEnum --> 多个值可以映射到一个名称(不是别名)
  • NoAliasEnum --> 具有相同值的名称不是别名(想想扑克牌)
  • OrderedEnum --> 根据定义,成员是顺序可比的
  • UniqueEnum --> 不允许使用别名

您可能对可以使用多个参数创建枚举感兴趣:

from enum import Enum

class NoValue(Enum):
    def __repr__(self):
        return '<%s.%s>' % (self.__class__.__name__, self.name)

Color = NoValue('Color', ['red', 'green', 'blue'])  # no need for "auto()" calls

这样您就不必使用 auto 或其他任何东西(例如 __prepare__)。


why was this (or something similar) decided against - with the auto feature being included in Python 3.6 instead?

这已在 Python 问题跟踪器(尤其是 bpo-23591)上进行了详细讨论,我将包括反对它的(总结)论点:

Vedran Čačić:

This is something fundamental: it is breaking the promise that class body is a suite of commands, where Python statements (such as assignment) have their usual semantics.

Raymond Hettinger:

As long as [auto] has been defined somewhere (i.e. from enum import [auto]), it is normal Python and doesn't fight with the rest of language or its toolchains.

简而言之:class 定义将这些 "variables" 解释为查找:

class A(object):
    a

但是对于 enum 它们应该被解释为作业?那个用例根本没有被考虑 "special enough to break the rules".