11.1 Python文化中的接口和协议
协议被定义为非正式接口,是让Python这种动态类型语言实现多态的方式
11.2 Python喜欢序列
图 11-1展示了定义为抽象基类的Sequence正式接口
示例11-3 定义__getitme__方法,只实现了序列协议的一部分,这样足够访问元素、迭代和使用in运算符了。
1 | python复制代码>>> class Foo: |
鉴于序列协议的重要性,如果没有__iter__和__contains__方法,Python会调用__getitem__方法,设法让迭代和in运算符可用。
11.3 使用猴子补丁在运行时实现协议
标注库中的random.shuffle函数用法如下:
1 | python复制代码>>> from random import shuffle |
示例11-5 random.shuffle函数不能打乱FrenchDeck实例
1 | arduino复制代码from frenchdeck import FrenchDeck |
这个报错的原因是,shuffle函数要调换collection中元素的位置,而FrenchDeck只实现了不可变序列的协议。可变的序列还必须提供__setitem__方法。
示例11-6 为FrenchDeck打猴子补丁,把它变成可变的
1 | scss复制代码def set_card(deck, position, card): |
猴子补丁:在运行时修改类或模块,而不改动源码。
11.4 Alex Martelli的水禽
11.5 定义抽象基类的子类
示例11-8 FrenchDeck2,collections.MutableSequence的子类
1 | python复制代码import collections |
insert这个抽象方法是必须实现的,导入时,Python不会检查抽象方法的实现,在运行时实例化FrenchDeck2类时才会真正检查。
1 | scala复制代码from frenchdeck2 import FrenchDeck2 |
图11-2 MutableSequence抽象基类
11.6 标准库中的抽象基类
11.6.1 collections.abc模块中的抽象基类
11.6.2 抽象基类的数字塔
numbers包定义的是“数字塔”(即各个抽象基类的层次结构是线性的),其中Number是位于最顶端的超类,随后是Complex子类,依次往下,最底端是Integral类:
- Number
- Complex
- Real
- Rational
- Integral
如果想检查一个数是不是整数,可以使用isinstance(x, numbers.Ingegral),这样的代码就能接受int、bool。
如果一个值可能是浮点数类型,可以使用isinstance(x, numbers.Real)检查。这样的代码能接受bool、int、float、fractions.Fraction。
11.7 定义并使用一个抽象基类
示例11-9 tombola.py: Tombola是抽象基类,有两个抽象方法和两个具体方法
1 | python复制代码import abc |
示例11-11 不符合Tombola要求的子类无法蒙混过关
1 | scala复制代码from tombola import Tombola |
尝试实例化Fake时抛出了TypeError。Python热舞Fake是抽象类,因为它没有实现load方法。
11.7.1 抽象基类语法详解
11.7.2 定义Tombola抽象基类的子类
示例11-12 bingo.py:BingoCage是Tombola的具体子类
1 | python复制代码import random |
示例11-13 lotto.py:LotteryBlower是Tombola的具体子类,覆盖了继承的inspect和loaded方法
1 | python复制代码import random |
11.7.3 Tombola的虚拟子类
即便不继承,也有办法把一个类注册为抽象基类的虚拟子类。这样做时,我保证注册的类忠实地实现了抽象基类定义的接口,而Python会相信我们,从而不做检查。如果我们说谎了,那么常规的运行时异常会被我们捕获。
注册虚拟子类的方式是在抽象基类上调用register方法。这样做之后,注册的类会变成抽象基类的虚拟子类,而且issubclass和isinstance等函数都能识别,但是注册的类不会从抽象基类中继承任何方法和属性。
示例11-14 tombolist.py:TomboList是Tombola的虚拟子类
1 | python复制代码from random import randrange |
1 | python复制代码 |
类的继承关系在一个特殊的类属性中指定————__mro__,即方法解析顺序(Method Resolution Order).这个属性的作用很简单,按顺序列出类及超类,Python会按照这个顺序搜索方法。查看TomboList类的__mro__属性,会发现它只列出了“真实的”超类。
1 | python复制代码>>> TomboList.__mro__ |
TomboList.__mro中没有Tombola,因此TomboList没有从Tombola中继承任何方法。
11.8 Tombola子类的测试方法
__subclasses__()
这个方法返回类的直接子类列表,不包含虚拟子类。
__abc_register
只有抽象基类有这个数据属性,其值是一个WeakSet对象,即抽象类注册的虚拟子类的弱引用。
11.9 Python使用register的方式
虽然现在可以把register当作装饰器使用了,但更常见的做法还是把它当作函数使用,用于注册其他地方定义的类。
把内置类型tuple、str、range和memoryview注册为Sequence的虚拟子类:
1 | python复制代码Sequence.register(tuple) |
11.10 鹅的行为有可能像鸭子
即便不注册,抽象基类也能把一个类识别为虚拟子类。
1 | python复制代码>>> class Struggle: |
Struggle是abc.Sized的子类,这是因为abc.Sized实现了一个特殊的类方法,名为__subclasshook__.
本文转载自: 掘金