子类扩展了超类的定义。要指定超类,可在 class
语句中的类名后加上超类名,并将其用圆括号括起。
class Filter:
def init(self):
self.blocked = []
def filter(self, sequence):
return [x for x in sequence if x not in self.blocked]
f = Filter()
f.init()
f.filter([1, 2, 3])
[1, 2, 3]
Filter
是一个过滤序列的通用类。实际上它不会过滤掉任何东西。
Filter
类的用途在于可用作其他类的基类(超类)。
SPAMFilter 是 Filter 的子类:
class SPAMFilter(Filter):
def init(self): # 重写超类Filter的方法init
self.blocked = ['SPAM']
s = SPAMFilter()
s.init()
s.filter(['SPAM', 'SPAM', 'SPAM', 'SPAM', 'eggs', 'bacon', 'SPAM'])
['eggs', 'bacon']
请注意 SPAMFilter
类的定义中有两个要点:
- 以提供新定义的方式重写了
Filter
类中方法init
的定义。 - 直接从
Filter
类继承了方法filter
的定义,因此无需重新编写其定义。
为说明如何继承多个类,下面来创建几个类。
class Calculator:
def calculate(self, expression):
self.value = eval(expression)
class Talker:
def talk(self):
print('Hi, my value is', self.value)
class TalkingCalculator(Calculator, Talker):
pass
子类 TalkingCalculator
本身未实现任务功能,其所有的行为都是从超类那里继承的:
从 Calculator
那里继承 calculate()
,并从 Talker
那里继承 talk()
,它成了会说话的计算器。
tc = TalkingCalculator()
tc.calculate('1 + 2 * 3')
tc.talk()
Hi, my value is 7
这被称为多重继承,从开发语言方面来讲是一个强大的功能。 然而,除非万不得已,否则应避免使用多重继承,因为在有些情况下,它可能带来意外的“并发症”。
使用多重继承时,有一点务必注意:如果多个超类以不同的方式实现了同一个方法
(即有多个同名方法),必须在 class
语句中小心排列这些超类,因为位于前面的类的方法将屏蔽位于后面的类的方法。
因此,在前面的示例中,如果 Calculator()
类包含方法 talk
,那么这个方法将覆盖 Talker
类的方法 talk()
(导致它不可访问)。
可以像下面这样反转超类的排列顺序:
class TalkingCalculator(Talker, Calculator): pass
将导致 Talker
的方法 talk
是可以访问的。
多个超类的超类相同时,查找特定方法或属性时访问超类的顺序称为方法解析顺序(MRO),它使用的算法非常复杂。
所幸其效果很好,可能根本无需担心。
接口这一概念与多态相关。处理多态对象时, 只关心其接口(协议),对外暴露的方法和属性。 在Python中,不显式地指定对象必须包含哪些方法才能用作参数。
通常要求对象遵循特定的接口(即实现特定的方法),但如果需要,也可非常灵活地提出要求: 不是直接调用方法并期待一切顺利,而是检查所需的方法是否存在; 如果不存在,就改弦易辙。
hasattr(tc, 'talk')
True
hasattr(tc, 'fnord')
False
在上述代码中,发现 tc
包含属性 talk
(指向一个方法),
但没有属性 fnord
。如果愿意,还可以检查属性 talk
是否是可调用的。
callable(getattr(tc, 'talk', None))
True
callable(getattr(tc, 'fnord', None))
False
请注意,这里没有在 if
语句中使用 hasattr
并直接访问属性,
而是使用了 getattr
(,然后对返回的对象调用 callable
。
注意:setattr
与 getattr
功能相反,可用于设置对象的属性:
setattr(tc, 'name', 'Mr. Gumby')
tc.name
'Mr. Gumby'
要查看对象中存储的所有值,可检查其 dict
属性。
如果要确定对象是由什么组成的,应 研究模块 inspect
。
这个模块主要供高级用户创建对象浏览器以及其他需要这种功能的类似程序。。
然而,有比手工检查各个方法更好的选择。在历史上的大部分时间内,Python几乎都只依赖于鸭子类型,
即假设所有对象都能完成其工作,同时偶尔使用 hasattr
来检查所需的方法是否存 在。
很多其他语言(如Java和Go)都采用显式指定接口的理念,而有些第三方模块提供了这种理 念的各种实现。
最终,Python通过引入模块 abc
提供了官方解决方案。这个模块为所谓的抽象基 类提供了支持。
一般而言,抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实 现的一组抽象方法。
下面是一个简单的示例:
from abc import ABC, abstractmethod
class Talker(ABC):
@abstractmethod
def talk(self):
pass
形如 @this
的东西被称为装饰器,
这里的要点是使用 @abstractmethod
来将方法标记为抽象的,
在子类中必须实现的方法。
注意:如果使用的是较旧的Python版本,将无法在模块 abc
中找到 ABC
类。
在这种情况下,需要 导入 ABCMeta
,并在类定义开头包含代码行 metaclass = ABCMeta
(紧跟在 class
语句后面并缩进)。
如果使用的是3.4之前的Python 3版本,也可使用 Talker(metaclass=ABCMeta)
代替 Talker(ABC)
。
抽象类(即包含抽象方法的类)最重要的特征是不能实例化。
>>> Talker()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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)
__main__.Herring
<class ' main .Herring'>
isinstance(h, Talker)
True
issubclass(Herring, Talker)
True
然而,这种做法存在一个缺点,就是直接从抽象类派生提供的保障没有了。
>>> class Clam:
...
pass
...
>>> Talker.register(Clam)
<class '__main__.Clam'>
>>> issubclass(Clam, Talker)
True
>>> c = Clam()
>>> isinstance(c, Talker)
True
>>> c.talk()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Clam' object has no attribute 'talk'