abc.ABCMeta抽象实例化检查为什么不对`list`和`dict`的派生类起作用?

时间:2019-08-08 08:03:23

标签: python metaclass abc

我一直在尝试使用python中的abc模块。啦

>>> import abc

在正常情况下,如果ABC类包含未实现的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。直到您从某事物继承...像下面这样说dictlist为止,一切似乎都可以正常工作:

>>> class YourClass(list, metaclass=abc.ABCMeta):
...     @abc.abstractmethod
...     def yourmethod(self):
...         return -1
...
>>> YourClass()
[]

这令人惊讶,因为无论如何,type可能是主要的工厂或元类类的东西。

>>> type(abc.ABCMeta)
<class 'type'>
>>> type(list)
<class 'type'>

通过一些调查,我发现它可以归结为简单的事情,就像在类的__abstractmethod__中添加object属性,其余的事情就自动发生了:

>>> 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中提取抽象类,这是否明智?还是应该在实例化之前添加__new__类方法来检查__abstractmethods__

1 个答案:

答案 0 :(得分:3)

问题

如果您有下一堂课:

from abc import ABC, abstractmethod
class Foo(list, ABC):
    @abstractmethod
    def yourmethod(self):
        pass

问题在于Foo的对象可以创建而没有任何错误,因为Foo.__new__(Foo)将呼叫直接委派给list.__new__(Foo)而不是{{1} }(负责检查所有抽象方法是否在要实例化的类中实现)

我们可以在Foo上实现ABC.__new__(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__的工作)。

类似这样的东西:

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()