Python基础教程 抽象基类

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的详细信息,请参阅标准库参考手册。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181031G0DNHE00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券