7.2.10 抽象基类
然而,有比手工检查各个方法更好的选择。在历史上的大部分时间内, Python几乎都只依赖于鸭子类型,即假设所有对象都能完成其工作,同时偶尔使用hasattr来检查所需的方法是否存在。很多其他语言(如Java和Go)都采用显式指定接口的理念,而有些第三方模块提供了这种理念的各种实现。最终, Python通过引入模块abc提供了官方解决方案。这个模块为所谓的抽象基类提供了支持。一般而言,抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实现的一组抽象方法。下面是一个简单的示例:
from abc import ABC, abstractmethod
class Talker(ABC):
@abstractmethod
def talk(self):
pass
形如@this的东西被称为装饰器,其用法将在第9章详细介绍。这里的要点是你使用@abstractmethod来将方法标记为抽象的——在子类中必须实现的方法。
注意 如果你使用的是较旧的Python版本,将无法在模块abc中找到ABC类。在这种情况下,需要导入ABCMeta,并在类定义开头包含代码行__metaclass__ = ABCMeta(紧跟在class语句后面并缩进)。如果你使用的是3.4之前的Python 3版本,也可使用Talker(metaclass=ABCMeta)代替Talker(ABC)。
抽象类(即包含抽象方法的类)最重要的特征是不能实例化。
>>> Talker()
Traceback (most recent call last):
File "", line 1, in
TypeError: Can't instantiate abstract class Talker with abstract methods talk
假设像下面这样从它派生出一个子类:
class Knigget(Talker):
pass
由于没有重写方法talk,因此这个类也是抽象的,不能实例化。如果你试图这样做,将出现类似于前面的错误消息。然而,你可重新编写这个类,使其实现要求的方法。
class Knigget(Talker):
def talk(self):
print("Ni!")
现在实例化它没有任何问题。这是抽象基类的主要用途,而且只有在这种情形下使用isinstance才是妥当的:如果先检查给定的实例确实是Talker对象,就能相信这个实例在需要的情况下有方法talk。
>>> k = Knigget()
>>> isinstance(k, Talker)
True
>>> k.talk()
Ni!
然而,还缺少一个重要的部分——让isinstance的多态程度更高的部分。正如你看到的,抽象基类让我们能够本着鸭子类型的精神使用这种实例检查!我们不关心对象是什么,只关心对象能做什么(它实现了哪些方法)。因此,只要实现了方法talk,即便不是Talker的子类,依然能够通过类型检查。下面来创建另一个类。
class Herring:
def talk(self):
print("Blub.")
这个类的实例能够通过是否为Talker对象的检查,可它并不是Talker对象。
>>> h = Herring()
>>> isinstance(h, Talker)
False
诚然,你可从Talker派生出Herring,这样就万事大吉了,但Herring可能是从他人的模块中导入的。在这种情况下,就无法采取这样的做法。为解决这个问题,你可将Herring注册为Talker(而不从Herring和Talker派生出子类),这样所有的Herring对象都将被视为Talker对象。
>>> Talker.register(Herring)
>>> isinstance(h, Talker)
True
>>> issubclass(Herring, Talker)
True
然而,这种做法存在一个缺点,就是直接从抽象类派生提供的保障没有了。
>>> class Clam:
... pass
...
>>> Talker.register(Clam)
>>> issubclass(Clam, Talker)
True
>>> c = Clam()
>>> isinstance(c, Talker)
True
>>> c.talk()
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'Clam' object has no attribute 'talk'
换而言之,应将isinstance返回True视为一种意图表达。在这里, Clam有成为Talker的意图。本着鸭子类型的精神,我们相信它能承担Talker的职责,但可悲的是它失败了。
标准库(如模块collections.abc)提供了多个很有用的抽象类,有关模块abc的详细信息,请参阅标准库参考手册。
领取专属 10元无门槛券
私享最新 技术干货