为什么 type(enumType) 在 python 中返回 EnumMeta ?

tom*_*cat 5 python python-3.x

from enum import Enum

class Color(Enum):
   RED=1;

>>>type(Color);
<class 'enum.EnumMeta'>
Run Code Online (Sandbox Code Playgroud)

我认为 Color 的类型应该是“class Enum”,但为什么它返回“enum.EnumMeta”?

这种行为与常见情况不同,幕后是什么?

met*_*ter 2

首先,Color您创建的类是该类型的直接子类Enum(来自模块enum),因此与Enum的关系Color实际上是 的父类(也称为超类)Color,或者Color是 的子类Enum

>>> from enum import Enum
>>> 
>>> class Color(Enum):
...     RED = 1
... 
>>> issubclass(Color, Enum)
True
Run Code Online (Sandbox Code Playgroud)

现在,它type()不仅仅是一个函数,尽管乍一看它可能看起来像一个函数,因为向它传递单个参数会揭示所传递参数的类型。一般来说,它可以确定支撑某些对象实例的底层类,例如:

>>> obj = object()
>>> type(obj)
<class 'object'>
Run Code Online (Sandbox Code Playgroud)

同样,如果您像这样实例化一个实例Color,并尝试查找结果类型:

>>> c = Color(1)
>>> type(c)
<enum 'Color'>
Run Code Online (Sandbox Code Playgroud)

到目前为止一切顺利,但是类定义本身的类型是什么,例如object

>>> type(object)
<class 'type'>
>>> class Demo(object):
...     pass
... 
>>> print(Demo)  # effectively calls `str(Demo)` before printing
<class '__main__.Demo'>
>>> type(Demo)
<class 'type'>
Run Code Online (Sandbox Code Playgroud)

这两者都越来越接近您所做的事情type(Color)- 您所做的就是有效地找到创建该类的底层基础。object在及其子类的情况下Demo,该基础都是type。这揭示了一些东西 - 这type不仅仅是一个函数,而且实际上是一个元类(滚动到“什么是元类(最后)”部分,其中深入了解了type实际是什么)。但简而言之,我们可以通过直接子类化来创建元类,而type不是object

>>> class SubType(type):
...     pass
Run Code Online (Sandbox Code Playgroud)

现在我们有一个 的子类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'>
Run Code Online (Sandbox Code Playgroud)

有了从上面的例子中获得的知识,特别是如何创建与问题类似的情况,这揭示了它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'>
Run Code Online (Sandbox Code Playgroud)

这就是问题中所看到的。尽管这是一个损坏的枚举类,只是因为它不具有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>
Run Code Online (Sandbox Code Playgroud)

虽然经过进一步的实验,仍然可以完成单行,但请注意,这是奇怪的,并且可能会随机停止工作,因为 1)_EnumDict不被视为公共接口,因为它的前缀是_; 2) 这取决于将来可能会被覆盖的 dunder 运算符 - 看看在之前的次要版本中3.9.1如何没有引入这一新更改3.9.2作为示例,简单的单行代码如何在后续版本中中断。现在,如果您准备好了,请系好安全带,因为这会滥用:=运算符 , any, mapzip以及运算符链乱七八糟,任何人都不应该编写。

>>> 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>
Run Code Online (Sandbox Code Playgroud)

为了提高可读性,我确实打破了这一长行,并且可以传递一个dict嵌套在类属性中的标准外观,以便使用结果类创建“易于使用”(是的,键和值可以作为单独的传递)列表,但我认为事后编辑不太友好)。关于如何将其组合在一起的解释将留给读者练习。