抽象基类的常见用途:实现接口时作为超类使用。然后,说明抽象基类如何检查具体子类是否符合接口定义,以及如何使用注册机制声明一个类实现了某个接口,而不进行子类化操作。最后,说明如何让抽象基类自动“识别”任何符合接口的类——不进行子类化或注册。
Python文化中的接口和协议
接口在动态类型语言中是怎么运作的呢?首先,基本的事实是,Python语言没有 interface 关键字,而且除了抽象基类,每个类都有接口:类实现或继承的公开属性(方法或数据属性),包括特殊方法,如__getitem__ 或 __add__。
按照定义,受保护的属性和私有属性不在接口中:即便“受保护的”属性也只是采用命名约定实现的(单个前导下划线);私有属性可以轻松地访问,原因也是如此。不要违背这些约定。
另一方面,不要觉得把公开数据属性放入对象的接口中不妥,因为如果需要,总能实现读值方法和设值方法,把数据属性变成特性,使用obj.attr 句法的客户代码不会受到影响。 Vector2d 类就是这么做的,Vector2d 类的第一版,x 和 y 是公开属性。
vector2d_v0.py:x 和 y 是公开数据属性
class Vector2d: def __init__(self, x, y): self.x = x self.y = y def __iter__(self): return (n for n in (self.x, self.y))
我们把 x 和 y 变成了只读特性。这是一项重大重构,但是 Vector2d 的接口基本没变:用户仍能读取my_vector.x 和 my_vector.y。
class Vector2d: def __init__(self, x, y): self.__x = x self.__y = y @property def x(self): return self.__x @property def y(self): return self.__y def __iter__(self): return (i for i in (self.x, self.y))
Python喜欢序列
Python 数据模型的哲学是尽量支持基本协议。对序列来说,即便是最简单的实现,Python 也会力求做到最好。
下图展示了定义为抽象基类的 Sequence 正式接口。
Sequence 抽象基类和 collections.abc 中相关抽象类的UML 类图,箭头由子类指向超类,以斜体显示的是抽象方法
现在,看看下面🌰中的 Foo 类。它没有继承 abc.Sequence,而且只实现了序列协议的一个方法: __getitem__ (没有实现 __len__ 方法)。
>>> class Foo:... def __getitem__(self, pos):... return range(0, 30, 10)[pos]...>>> f = Foo()>>> f[1]>>> for i in f: print(i)...>>> 20 in fTrue>>> 15 in fFalse
虽然没有 __iter__ 方法,但是 Foo 实例是可迭代的对象,因为发现有__getitem__ 方法时,Python 会调用它,传入从 0 开始的整数索引,尝试迭代对象(这是一种后备机制)。尽管没有实现 __contains__ 方法,但是 Python 足够智能,能迭代 Foo 实例,因此也能使用 in 运算符:Python 会做全面检查,看看有没有指定的元素。
新闻热点
疑难解答