我一直在尝试使用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。直到您从某事物继承...像下面这样说dict
或list
为止,一切似乎都可以正常工作:
>>> 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实现是否都一样。
最后,请问问题……为什么要使其表现得如此?我们不应该从list
,tuple
或dict
中提取抽象类,这是否明智?还是应该在实例化之前添加__new__
类方法来检查__abstractmethods__
?
答案 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()