from enum import Enum
class Color(Enum):
RED=1;
>>>type(Color);
<class 'enum.EnumMeta'>
我认为Color的类型应该是"类枚举",但为什么它返回"enum.EnumMeta"?
这个行为不同于一般情况,是什么在引擎盖下?
首先,您创建的类Color
是类型Enum
的直接子类(来自模块enum
),因此Enum
与Color
的关系实际上是Color
的父类(又称超类),或者Color
是Enum
的子类:
>>> from enum import Enum
>>>
>>> class Color(Enum):
... RED = 1
...
>>> issubclass(Color, Enum)
True
现在,type()
不仅仅是一个函数,而且是一个函数,尽管起初它可能看起来像一个函数,因为向它传递一个参数会显示传递的参数的类型。通常,它可以确定支持某些对象实例的底层类,例如:
>>> obj = object()
>>> type(obj)
<class 'object'>
同样,如果您像这样实例化Color
的实例,并尝试找到结果类型:
>>> c = Color(1)
>>> type(c)
<enum 'Color'>
到目前为止都很好,但是类定义本身的类型是什么,比如object
?
>>> type(object)
<class 'type'>
>>> class Demo(object):
... pass
...
>>> print(Demo) # effectively calls `str(Demo)` before printing
<class '__main__.Demo'>
>>> type(Demo)
<class 'type'>
这两个都更接近您对type(Color)
所做的事情-您所做的是有效地找到创建类的底层基础。在object
及其子类Demo
的情况下,该基础是type
。这揭示了一些东西-type
不只是一个函数,但实际上是一个元类(滚动到"什么是元类(最后)"部分,其中深入了解type
实际上是什么)。但简单地说,我们可以通过直接从type
而不是object
继承子类来创建元类:
>>> class SubType(type):
... pass
现在我们有一个type
的子类,它可以用作metaclass
。现在,创建一个新类,并通过以下操作将metaclass
指定为SubType
。
>>> class Demo2(metaclass=SubType):
... pass
...
>>> class Demo3(Demo2):
... pass
...
>>> print(Demo2) # effectively calls `str(Demo2)` before printing
<class '__main__.Demo2'>
>>> type(Demo2)
<class '__main__.SubType'>
>>> type(Demo3)
<class '__main__.SubType'>
有了从上面的例子中获得的知识,特别是如何创建类似问题的情况,这表明Enum
及其子类将EnumMeta
作为底层元类,而type(Enum)
和type(Color)
只是揭示了这一事实。实际上,您还可以创建一个新的类,将EnumMeta
作为元类,如下所示:
>>> from enum import EnumMeta
>>>
>>> class BrokenEnum(metaclass=EnumMeta):
... pass
...
>>> print(BrokenEnum) # effective calls `str(BrokenEnum)` before printing
<enum 'BrokenEnum'>
>>> type(BrokenEnum)
<class 'enum.EnumMeta'>
因此,这是在问题中看到的。虽然这是一个坏的枚举类,只是因为它没有EnumMeta
期望这种类型的类正确工作的完整机制,因此应该咨询参考实现Enum
,以确保结果类可以像Enum
类一样工作。
所以有些人可能想知道type
的三个参数调用如何适合这里?由于简单的一行代码可以动态地生成一个类(即type('Cls', (object,), {})
实际上是class Cls(object): pass
),这可以很容易地为Enum
?实际上不是,因为元类的__prepare__
类方法可以潜在地用于提供自定义映射,包括任何映射表的子类,所以必须首先使用它来调用以产生一个(因为前面调用type
不会调用这个__prepare__
类方法-Enum
有这个__prepare__
类方法来产生enum_EnumDict
实例)。因此,要使用type
复制第一个定义,可以执行以下操作:
>>> clsdict = EnumMeta.__prepare__('Color2', (Enum,),)
>>> clsdict['RED'] = 1
>>> Color2 = EnumMeta('Color2', (Enum,), clsdict) # `type` may be used instead of `EnumMeta`, but this is to be consistent
>>> Color2(1)
<Color2.RED: 1>
虽然经过进一步的实验,仍然可以完成一行代码,但要注意,这是怪诞的,可能会随机停止工作,因为1)_EnumDict
不被认为是公共接口,因为它以_
为前缀;2)这依赖于一个可能在将来被覆盖的dunder操作符-看看3.9.1
在3.9.2
中如何没有引入这个新更改,作为一个简单的一行代码如何在后续版本中中断的例子。现在,如果你已经准备好了,那就准备好吧,因为这会滥用:=
操作符,any
,map
,zip
,以及操作符链,造成混乱,任何人都不应该写。
>>> Color3 = EnumMeta('Color3', (Enum,), (_ := EnumMeta.__prepare__(
... 'Color3', (Enum,),)) and any(map(_.__setitem__, *(zip(*{
... 'RED': 1,
... 'GREEN': 2,
... 'BLUE': 3,
... }.items())))) or _
... )
>>> Color3(1)
<Color3.RED: 1>
>>> Color(2)
<Color.GREEN: 2>
>>> Color(3)
<Color.BLUE: 3>
我打破了一个长行可读性,并使它有可能传入一个标准的dict
嵌套在类属性中,以创建与结果类的"易用性"。(是的,键和值可以作为单独的列表传递,但我认为这样编辑起来不太友好)。关于如何将它们组合在一起的解释将留给读者作为练习。