Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/python-3.x/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 使用ABCMeta和EnumMeta抽象枚举类 简单例子_Python_Python 3.x_Metaclass - Fatal编程技术网

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上是这样)。