使用ABCMeta和EnumMeta的抽象Enum类 [英] Abstract Enum Class using ABCMeta and EnumMeta

查看:305
本文介绍了使用ABCMeta和EnumMeta的抽象Enum类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目标是通过从abc.ABCMetaenum.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来自AIntEnum.

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

问题

为什么使用从EnumMetaABCMeta派生的元类的类有效地忽略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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆