使用ABCMeta和EnumMeta的抽象Enum类 [英] Abstract Enum Class using ABCMeta and EnumMeta
问题描述
目标是通过从abc.ABCMeta
和enum.EnumMeta
派生的元类创建抽象的枚举类.例如:
The goal is to create an abstract enum class through a metaclass deriving from both abc.ABCMeta
and enum.EnumMeta
. For example:
import abc
import enum
class ABCEnumMeta(abc.ABCMeta, enum.EnumMeta):
pass
class A(abc.ABC):
@abc.abstractmethod
def foo(self):
pass
class B(A, enum.IntEnum, metaclass=ABCEnumMeta):
X = 1
class C(A):
pass
现在,在Python3.7上,此代码将无错误地解释(在3.6.x上,大概在下面,也不会).实际上,一切看起来都很不错,我们的MRO显示B
来自A
和IntEnum
.
Now, on Python3.7, this code will be interpreted without error (on 3.6.x and presumably below, it will not). In fact, everything looks great, our MRO shows B
derived from both A
and IntEnum
.
>>> B.__mro__
(<enum 'B'>, __main__.A, abc.ABC, <enum 'IntEnum'>, int, <enum 'Enum'>, object)
抽象枚举不是抽象
但是,即使尚未定义B.foo
,我们仍然可以实例化B
而没有任何问题,然后调用foo()
.
Abstract Enum is not Abstract
However, even though B.foo
has not been defined, we can still instantiate B
without any issue, and call foo()
.
>>> B.X
<B.X: 1>
>>> B(1)
<B.X: 1>
>>> B(1).foo()
这似乎很奇怪,因为即使我使用自定义元类,也无法实例化从ABCMeta派生的任何其他类.
This seems rather weird, since any other class that derives from ABCMeta cannot be instantiated, even if I use a custom metaclass.
>>> class NewMeta(type):
... pass
...
... class AbcNewMeta(abc.ABCMeta, NewMeta):
... pass
...
... class D(metaclass=NewMeta):
... pass
...
... class E(A, D, metaclass=AbcNewMeta):
... pass
...
>>> E()
TypeError: Can't instantiate abstract class E with abstract methods foo
问题
为什么使用从EnumMeta
和ABCMeta
派生的元类的类有效地忽略ABCMeta
,而任何其他使用从ABCMeta
派生的元类的类呢?即使我自定义了__new__
运算符,也是如此.
Question
Why does a class using a metaclass derived from EnumMeta
and ABCMeta
effectively ignore ABCMeta
, while any other class using a metaclass derived from ABCMeta
use it? This is true even if I custom define the __new__
operator.
class ABCEnumMeta(abc.ABCMeta, enum.EnumMeta):
def __new__(cls, name, bases, dct):
# Commented out lines reflect other variants that don't work
#return abc.ABCMeta.__new__(cls, name, bases, dct)
#return enum.EnumMeta.__new__(cls, name, bases, dct)
return super().__new__(cls, name, bases, dct)
我很困惑,因为这似乎在面对元类是什么的时候:元类应该定义该类的定义和行为方式,在这种情况下,要使用一个既抽象又抽象的元类来定义一个类.枚举创建一个类,该类静默地忽略抽象组件.这是错误,是故意的,还是我不了解的更大的东西?
I'm rather confused, since this seems to fly in the face of what a metaclass is: the metaclass should define how the class is defined and behaves, and in this case, defining a class using a metaclass that is both abstract and an enumeration creates a class that silently ignores the abstract component. Is this a bug, is this intended, or is there something greater I am not understanding?
推荐答案
如@chepner的回答所述,发生的是Enum
元类覆盖了普通元类的__call__
方法,因此,Enum
永远不会通过常规方法实例化该类,因此,ABCMeta
检查不会触发其抽象方法检查.
As stated on @chepner's answer, what is going on is that Enum
metaclass overrides the normal metaclass' __call__
method, so that an Enum
class is never instantiated through the normal methods, and thus, ABCMeta
checking does not trigger its abstractmethod check.
但是,在创建类时,元类的__new__
会正常运行,并且抽象类机制用来将类标记为抽象的属性会在创建的类上创建属性___abstractmethods__
.
However, on class creation, the Metaclass's __new__
is run normally, and the attributes used by the abstract-class mechanisms to mark the class as abstract do create the attribute ___abstractmethods__
on the created class.
因此,您要做的所有工作就是进一步自定义元类,以执行对__call__
的代码中的抽象检查:
So, all you have to do for what you intend to work, is to further customize your metaclass to perform the abstract check in the code to __call__
:
import abc
import enum
class ABCEnumMeta(abc.ABCMeta, enum.EnumMeta):
def __call__(cls, *args, **kw):
if getattr(cls, "__abstractmethods__", None):
raise TypeError(f"Can't instantiate abstract class {cls.__name__} "
f"with frozen methods {set(cls.__abstractmethods__)}")
return super().__call__(*args, **kw)
这将使B(1)
表达式失败,并出现与abstractclass
实例化相同的错误.
This will make the B(1)
expression to fail with the same error as abstractclass
instantiation.
但是,请注意,无论如何都不能进一步继承Enum
类,并且在没有缺少抽象方法的情况下简单地创建它可能已经违反了您要检查的内容.也就是说:在上面的示例中,即使缺少foo
方法,也可以声明class B
并且B.x
将起作用.如果要防止这种情况,只需将相同的检查放入元类的__new__
:
Note, however, that an Enum
class can't be further inherited anyway, and it simply creating it without the missing abstractmethods may already violate what you want to check. That is: in your example above, class B
can be declared and B.x
will work, even with the missing foo
method. If you want to prevent that, just put the same check in the metaclass' __new__
:
import abc
import enum
class ABCEnumMeta(abc.ABCMeta, enum.EnumMeta):
def __new__(mcls, *args, **kw):
cls = super().__new__(mcls, *args, **kw)
if issubclass(cls, enum.Enum) and getattr(cls, "__abstractmethods__", None):
raise TypeError("...")
return cls
def __call__(cls, *args, **kw):
if getattr(cls, "__abstractmethods__", None):
raise TypeError(f"Can't instantiate abstract class {cls.__name__} "
f"with frozen methods {set(cls.__abstractmethods__)}")
return super().__call__(*args, **kw)
(不幸的是,CPython中的ABC
抽象方法检查似乎是在ABCMeta.__call__
方法之外的本机代码中执行的-否则,代替模仿错误,我们可以调用ABCMeta.__call__
显式覆盖super
的行为,而不是在那里对TypeError
进行硬编码.)
(Unfortunatelly, the ABC
abstract method check in CPython seems to be performed in native code, outside the ABCMeta.__call__
method - otherwise, instead of mimicking the error, we could just call ABCMeta.__call__
explicitly overriding super
's behavior instead of hardcoding the TypeError
there.)
这篇关于使用ABCMeta和EnumMeta的抽象Enum类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!