使用抽象基类与普通继承
我正在尝试理解使用抽象基类的好处。考虑这两段代码:
抽象基类:
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
,那么你的代码不会在编写时就出错,而是在你真正调用那个方法或属性的时候才会出错。
除此之外,使用抽象基类还有什么不同之处呢?
1 个回答
在你提问中提到的内容之外,最重要的具体答案是,如果一个类使用了 @abstractmethod
或 @abstractproperty
装饰器,并且继承自 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
不过,这里还有更多的内容。抽象基类(Abstract Base Classes)是在 Python 中引入的,具体可以参考 PEP 3119。我建议你看看“理由”这一部分,了解一下 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