为什么 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。这一切似乎都很好,直到你从某些东西继承......说 dictlist 如下所示:

>>> 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 实现。

最后,回到问题...为什么要这样做?我们不应该从 listtupledict 中抽象出 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()