为什么 abc.ABCMeta 抽象实例化检查对 `list` 和 `dict` 的衍生物不起作用?
Why doesn't the abc.ABCMeta abstract instantiation check work on derivatives of `list` and `dict`?
我已经对 python 中的 abc
模块进行了一些试验。阿啦
>>> import abc
在正常情况下,如果 ABC class 包含未实现的 abstractmethod
,您希望它不会被实例化。你知道如下:
>>> class MyClass(metaclass=abc.ABCMeta):
... @abc.abstractmethod
... def mymethod(self):
... return -1
...
>>> MyClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyClass with abstract methods mymethod
OR 对于任何派生的 Class。这一切似乎都很好,直到你从某些东西继承......说 dict
或 list
如下所示:
>>> class YourClass(list, metaclass=abc.ABCMeta):
... @abc.abstractmethod
... def yourmethod(self):
... return -1
...
>>> YourClass()
[]
这很令人惊讶,因为 type
可能是主要工厂或 metaclass 左右的东西,或者我从以下假设。
>>> type(abc.ABCMeta)
<class 'type'>
>>> type(list)
<class 'type'>
通过一些调查,我发现它可以归结为向 class' object
添加一个 __abstractmethod__
属性这样简单的事情,其余的将自行发生:
>>> class AbstractClass:
... pass
...
>>> AbstractClass.__abstractmethods__ = {'abstractmethod'}
>>> AbstractClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class AbstractClass with abstract methods abstractmethod
因此,可以通过故意覆盖 __new__
方法并清除 __abstractmethods__
来简单地避免检查,如下所示:
>>> class SupposedlyAbstractClass(metaclass=abc.ABCMeta):
... def __new__(cls):
... cls.__abstractmethods__ = {}
... return super(AbstractClass, cls).__new__(cls)
... @abc.abstractmethod
... def abstractmethod(self):
... return -1
...
>>> SupposedlyAbstractClass()
<__main__.SupposedlyAbstractClass object at 0x000001FA6BF05828>
这种行为在 Python 2.7 和 Python 3.7 中是相同的,正如我亲自检查过的那样。我不知道这是否适用于所有其他 python 实现。
最后,回到问题...为什么要这样做?我们不应该从 list
、tuple
或 dict
中抽象出 classes 是否明智?或者我应该继续添加一个 __new__
class 方法在实例化之前检查 __abstractmethods__
吗?
问题
如果你有下一个class:
from abc import ABC, abstractmethod
class Foo(list, ABC):
@abstractmethod
def yourmethod(self):
pass
问题是 Foo
的对象可以 创建而不会出现任何错误,因为 Foo.__new__(Foo)
将调用直接委托给 list.__new__(Foo)
ABC.__new__(Foo)
的(负责检查所有抽象方法是否在将要实例化的 class 中实现)
我们可以在 Foo 上实现 __new__
并尝试调用 ABC.__new__
:
class Foo(list, ABC):
def __new__(cls, *args, **kwargs):
return ABC.__new__(cls)
@abstractmethod
def yourmethod(self):
pass
Foo()
但是他引发了下一个错误:
TypeError: object.__new__(Foo) is not safe, use list.__new__()
这是由于 ABC.__new__(Foo)
调用了 object.__new__(Foo)
,这似乎在 Foo
继承自 list
时是不允许的
可能的解决方案
您可以在 Foo.__new__
上添加额外的代码,以检查要实例化的 class 中的所有抽象方法是否都已实现(基本上完成 ABC.__new__
的工作)。
像这样:
class Foo(list, ABC):
def __new__(cls, *args, **kwargs):
if hasattr(cls, '__abstractmethods__') and len(cls.__abstractmethods__) > 0:
raise TypeError(f"Can't instantiate abstract class {cls.__name__} with abstract methods {', '.join(cls.__abstractmethods__)}")
return super(Foo, cls).__new__(cls)
@abstractmethod
def yourmethod(self):
return -1
现在 Foo()
引发错误。但是接下来的代码运行没有任何问题:
class Bar(Foo):
def yourmethod(self):
pass
Bar()
我已经对 python 中的 abc
模块进行了一些试验。阿啦
>>> import abc
在正常情况下,如果 ABC class 包含未实现的 abstractmethod
,您希望它不会被实例化。你知道如下:
>>> class MyClass(metaclass=abc.ABCMeta):
... @abc.abstractmethod
... def mymethod(self):
... return -1
...
>>> MyClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyClass with abstract methods mymethod
OR 对于任何派生的 Class。这一切似乎都很好,直到你从某些东西继承......说 dict
或 list
如下所示:
>>> class YourClass(list, metaclass=abc.ABCMeta):
... @abc.abstractmethod
... def yourmethod(self):
... return -1
...
>>> YourClass()
[]
这很令人惊讶,因为 type
可能是主要工厂或 metaclass 左右的东西,或者我从以下假设。
>>> type(abc.ABCMeta)
<class 'type'>
>>> type(list)
<class 'type'>
通过一些调查,我发现它可以归结为向 class' object
添加一个 __abstractmethod__
属性这样简单的事情,其余的将自行发生:
>>> class AbstractClass:
... pass
...
>>> AbstractClass.__abstractmethods__ = {'abstractmethod'}
>>> AbstractClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class AbstractClass with abstract methods abstractmethod
因此,可以通过故意覆盖 __new__
方法并清除 __abstractmethods__
来简单地避免检查,如下所示:
>>> class SupposedlyAbstractClass(metaclass=abc.ABCMeta):
... def __new__(cls):
... cls.__abstractmethods__ = {}
... return super(AbstractClass, cls).__new__(cls)
... @abc.abstractmethod
... def abstractmethod(self):
... return -1
...
>>> SupposedlyAbstractClass()
<__main__.SupposedlyAbstractClass object at 0x000001FA6BF05828>
这种行为在 Python 2.7 和 Python 3.7 中是相同的,正如我亲自检查过的那样。我不知道这是否适用于所有其他 python 实现。
最后,回到问题...为什么要这样做?我们不应该从 list
、tuple
或 dict
中抽象出 classes 是否明智?或者我应该继续添加一个 __new__
class 方法在实例化之前检查 __abstractmethods__
吗?
问题
如果你有下一个class:
from abc import ABC, abstractmethod
class Foo(list, ABC):
@abstractmethod
def yourmethod(self):
pass
问题是 Foo
的对象可以 创建而不会出现任何错误,因为 Foo.__new__(Foo)
将调用直接委托给 list.__new__(Foo)
ABC.__new__(Foo)
的(负责检查所有抽象方法是否在将要实例化的 class 中实现)
我们可以在 Foo 上实现 __new__
并尝试调用 ABC.__new__
:
class Foo(list, ABC):
def __new__(cls, *args, **kwargs):
return ABC.__new__(cls)
@abstractmethod
def yourmethod(self):
pass
Foo()
但是他引发了下一个错误:
TypeError: object.__new__(Foo) is not safe, use list.__new__()
这是由于 ABC.__new__(Foo)
调用了 object.__new__(Foo)
,这似乎在 Foo
继承自 list
可能的解决方案
您可以在 Foo.__new__
上添加额外的代码,以检查要实例化的 class 中的所有抽象方法是否都已实现(基本上完成 ABC.__new__
的工作)。
像这样:
class Foo(list, ABC):
def __new__(cls, *args, **kwargs):
if hasattr(cls, '__abstractmethods__') and len(cls.__abstractmethods__) > 0:
raise TypeError(f"Can't instantiate abstract class {cls.__name__} with abstract methods {', '.join(cls.__abstractmethods__)}")
return super(Foo, cls).__new__(cls)
@abstractmethod
def yourmethod(self):
return -1
现在 Foo()
引发错误。但是接下来的代码运行没有任何问题:
class Bar(Foo):
def yourmethod(self):
pass
Bar()