Python 使用ABCMeta和EnumMeta抽象枚举类 简单例子
目标是通过派生自Python 使用ABCMeta和EnumMeta抽象枚举类 简单例子,python,python-3.x,metaclass,Python,Python 3.x,Metaclass,目标是通过派生自abc.ABCMeta和enum.EnumMeta的元类创建一个抽象枚举类。例如: 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=ABCEnumMe
abc.ABCMeta
和enum.EnumMeta
的元类创建一个抽象枚举类。例如:
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显示了从A
和IntEnum
派生的B
>>> B.__mro__
(<enum 'B'>, __main__.A, abc.ABC, <enum 'IntEnum'>, int, <enum 'Enum'>, object)
这似乎很奇怪,因为从ABCMeta派生的任何其他类都不能实例化,即使我使用自定义元类
>>> 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
派生的元类的任何其他类都会使用它?即使我自定义定义了\uuuu new\uuu
操作符,这也是正确的
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)
我相当困惑,因为这似乎与元类是什么背道而驰:元类应该定义类的定义和行为,在这种情况下,使用抽象和枚举的元类定义类会创建一个默默忽略抽象组件的类。这是一个bug,是有意为之,还是有更大的问题我不理解?调用枚举类型不会创建新实例。枚举类型的成员在类创建时由元类立即创建。
\uuu new\uu
方法只是执行查找,这意味着永远不会调用ABCMeta
来阻止实例化
B(1).foo()
之所以有效,是因为一旦有了实例,该方法是否标记为抽象并不重要。它仍然是一个真正的方法,可以这样调用。正如@chepner的回答中所述,Enum
元类重写了普通元类的\u call\u
方法,因此Enum
类永远不会通过普通方法实例化,因此,ABCMeta
检查不会触发其abstractmethod检查
但是,在创建类时,元类的\uuuuuu new\uuuuuu
正常运行,抽象类机制用于将类标记为抽象类的属性会在创建的类上创建属性\uuuuuuuuuuuuuuuuuuuuuuuuuuu abstractmethods
因此,您要做的就是进一步定制您的元类,以便在代码中执行抽象检查,以调用:
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)
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)
这将使B(1)
表达式失败,产生与abstractclass
实例化相同的错误
但是,请注意,Enum
类无论如何都不能被进一步继承,它只是在没有缺少抽象方法的情况下创建它,可能已经违反了您想要检查的内容。也就是说:在上面的示例中,可以声明class B
,并且B.x
将起作用,即使缺少foo
方法。如果要防止这种情况发生,只需在元类“\uuuu new\uuuu
中进行相同的检查:
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)
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.\uu调用
方法之外的本机代码中执行的-否则,我们可以调用ABCMeta.\uu调用
显式重写super
的行为,而不是硬编码TypeError那里。)B(1)
没有创建B
@chepner的实例,请将其标记为答案,因为我在您的评论中进行了更深入的挖掘,因此找到了以下内容:将其写下来作为答案,然后我会将其标记为答案。谢谢。我没有意识到所有的类构造都是由元类完成的,这在回顾时显然是有意义的。我不太确定凹痕足以做到这一点;我一直在深入研究enum.py
,以确切了解EnumMeta
的功能。这不起作用(至少在3.6上是这样)。