将 __prepare__ 用于枚举...有啥问题?
Posted
技术标签:
【中文标题】将 __prepare__ 用于枚举...有啥问题?【英文标题】:Using __prepare__ for an Enum ... what's the catch?将 __prepare__ 用于枚举...有什么问题? 【发布时间】:2017-10-06 21:30:28 【问题描述】:Python 的enum.Enum
的声明式使用需要提供值,而在枚举的最基本用例中,我们实际上并不关心名称和值。我们只关心哨兵本身。最近看了一篇related Q&A,发现可以使用枚举元类的__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
来帮助解决 omitting values 的问题,但界面仍然很奇怪 - 您需要为每个成员指定 auto()
值,并从不同的成员继承修复__repr__
的基础:
class Color(NoValue):
red = auto()
blue = auto()
green = auto()
知道为标准库选择的实现已经投入了许多工时和非常谨慎的工作,因此之前演示的可以说更 Pythonic 的声明性枚举版本不能正常工作肯定是有原因的。
我的问题是,提议的方法有哪些问题和失败模式,为什么决定反对(或类似的方法) - 而 Python 3.6 中包含 auto
功能?
【问题讨论】:
【参考方案1】:将defaultdict
用作枚举的命名空间有几个缺陷:
_EnumDict
命名空间:
覆盖成员
覆盖方法
较新的_generate
方法
还有最重要的:
它不起作用为什么它不起作用? __prepare__
不仅可以在命名空间字典上设置属性,命名空间字典本身也可以 - 而_EnumDict
可以:_member_names
,应该是成员的所有属性的列表。
然而,声明一个不带值的名称并非不可能——aenum
1 包允许它通过一些保护措施:
property
、classmethod
和 staticmethod
默认被排除在外,但可以包含它们和/或排除其他全局名称
不过,这种行为对于 stdlib 来说太神奇了,所以如果你想要它以及其他一些增强/改进2,你必须使用aenum
。
一个例子:
from aenum import AutoEnum
class Color(AutoEnum):
red
green
blue
__repr__
仍然显示创建的值。
--
1 披露:我是Python stdlib Enum
、enum34
backport 和Advanced Enumeration (aenum
) 库的作者。
2NamedConstant
(就像它说的那样;),NamedTuple
(基于元类,默认值等),以及一些内置的枚举:
MultiValueEnum
--> 多个值可以映射到一个名称(不是别名)
NoAliasEnum
--> 具有相同值的名称不是别名(想想扑克牌)
OrderedEnum
--> 成员在定义上是可排序的
UniqueEnum
--> 不允许使用别名
【讨论】:
有趣,谢谢!我刚刚简要浏览了AutoEnum
的实现,但使用_generate_next_value_
的东西很快就深入了。可以说元类使用与我的问题中描述的相同的基本技巧,还是完全不同的实现?
@wim:类似:defaultdict
使用__missing__
方法,而_EnumDict
捕获__getitem__
查找并从那里处理。
@wim:aenum
的复杂性在于支持 Python 2.x 系列。几天前我打开了Enum
的stdlib 版本,感觉就像呼吸了新鲜空气!
嗯,文档中没有对 aenum
的单一引用。对于可能希望更神奇的用户来说,这似乎是一个很好的参考。
@JimFasarakisHilliard:Python 文档不习惯引用外部项目(我相信只有几个例外)。不过也许是维基……【参考方案2】:
您可能对可以使用多个参数创建枚举感兴趣:
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__
)。
为什么决定反对这个(或类似的东西) - 而自动功能被包含在 Python 3.6 中?
这已在 Python 问题跟踪器(特别是 bpo-23591)上进行了详细讨论,我将包括(总结)反对它的论点:
Vedran Čačić:
这是基本的:它违背了类主体是一组命令的承诺,其中 Python 语句(例如赋值)具有它们通常的语义。
Raymond Hettinger:
只要 [auto] 已在某处定义(即 from enum import [auto]),它就是普通 Python,不会与其他语言或其工具链发生冲突。
简而言之:类定义将这些“变量”解释为查找:
class A(object):
a
但是对于enum
,它们应该被解释为分配吗?这个用例根本不被考虑"special enough to break the rules"。
【讨论】:
是的,我知道函数式接口(我不喜欢它)......这就是问题开始的原因:“Declarative 使用 Python 的枚举...”。为相关讨论的链接 +1,不过以上是关于将 __prepare__ 用于枚举...有啥问题?的主要内容,如果未能解决你的问题,请参考以下文章