我试图理解使用抽象基类的好处。考虑这两段代码:
抽象基类:
from abc import ABCMeta, abstractmethod, abstractproperty
class CanFly:
__metaclass__ = ABCMeta
@abstractmethod
def fly(self):
pass
@abstractproperty
def speed(self):
pass
class Bird(CanFly):
def __init__(self):
self.name = 'flappy'
@property
def speed(self):
return 1
def fly(self):
print('fly')
b = Bird()
print(isinstance(b, CanFly)) # True
print(issubclass(Bird, CanFly)) # True
普通继承:
class CanFly(object):
def fly(self):
raise NotImplementedError
@property
def speed(self):
raise NotImplementedError()
class Bird(CanFly):
@property
def speed(self):
return 1
def fly(self):
print('fly')
b = Bird()
print(isinstance(b, CanFly)) # True
print(issubclass(Bird, CanFly)) # True
如您所见,这两种方法都支持使用isinstance
和issubclass
的变化。
现在,我知道的一个区别是,如果你试图实例化一个抽象基类的子类而不重写所有的抽象方法/属性,你的程序将会失败。但是,如果您对NotImplementedError
使用纯继承,您的代码将不会失败,直到您实际调用有问题的方法/属性。
除此之外,使用抽象基类有什么不同?
在具体细节方面,除了你在问题中提到的,最值得注意的答案是@abstractmethod
或@abstractproperty
1装饰器的存在,以及从ABC
继承(或拥有ABCMeta
元类)阻止了你实例化对象。
from abc import ABC, abstractmethod
class AbsParent(ABC):
@abstractmethod
def foo(self):
pass
AbsParent()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class AbsParent with abstract methods foo
然而,这里还有更多的因素在起作用。抽象基类是在PEP 3119中引入Python的。我建议你通读一下《基本原理》。一节中,Guido阐述了它们最初被引入的原因。我的幼稚总结是,他们不太关心他们的具体特征,而更关心他们的理念。它们的目的是向外部检查器发出信号,表明对象继承了ABC,因为它继承了ABC,所以它将遵循善意的协议。本"诚信协议"子对象遵循父对象的意图。本协议的实际执行由您来决定,这就是为什么它是一个善意的协议,而不是一个明确的合同。
这主要通过register()
方法的镜头显示。任何将ABCMeta
作为其元类(或简单地从ABC
继承)的类都将在其上具有register()
方法。通过将类注册为ABC,您就表示它继承了ABC,尽管从技术上讲它并没有继承。这就是善意协议的由来。
from abc import ABC, abstractmethod
class MyABC(ABC):
@abstractmethod
def foo(self):
"""should return string 'foo'"""
pass
class MyConcreteClass(object):
def foo(self):
return 'foo'
assert not isinstance(MyConcreteClass(), MyABC)
assert not issubclass(MyConcreteClass, MyABC)
虽然MyConcreteClass
此时与MyABC
无关,但它确实根据注释中列出的要求实现了MyABC
的API。现在,如果我们将MyConcreteClass
注册为MyABC
,它将通过isinstance
和issubclass
检查。
MyABC.register(MyConcreteClass)
assert isinstance(MyConcreteClass(), MyABC)
assert issubclass(MyConcreteClass, MyABC)
再一次,这是"善意协议"的地方;开始发挥作用。不必遵循MyABC
中列出的API。通过向ABC注册具体类,我们告诉任何外部检查员,我们,程序员,正在遵守我们应该遵守的API。
1注意@abstractproperty
不再是首选。你应该使用:
@property
@abstractmethod
def foo(self):
pass