如果用过 Java、C# 或类似的语言,你会觉得鸭子类型的非正式协议很新奇。但是对长时间使用 Python 或 Ruby 的程序员来说,这是接口的“常规”方式,新知识是抽象基类的严格规定和类型检查。Python 语言诞生 15 年后,Python 2.6 才引入抽象基类。

Python 社区以往对接口的不严谨理解:部分实现接口通常被认为是可接受的。我们将通过几个示例强调鸭子类型的动态本性,从而澄清这一点。

抽象基类与描述符和元类一样,是用于构建框架的工具。因此,只有少数Python 开发者编写的抽象基类不会对用户施加不必要的限制,让他们做无用功。

引入抽象基类之前,Python 就已经非常成功了,即便现在也很少有代码使用抽象基类。之前我们提到过鸭子类型和协议,我们把协议定义为非正式的接口,是让Python 这种动态类型语言实现多态的方式。

接口在动态类型语言中是怎么运作的呢?首先,基本的事实是,Python 语言没有interface 关键字,而且除了抽象基类,每个类都有接口:类实现或继承的公开属性(方法或数据属性),包括特殊方法,如 __getitem____add__

按照定义,受保护的属性和私有属性不在接口中:即便“受保护的”属性也只是采用命名约定实现的(单个前导下划线);私有属性可以轻松地访问原因也是如此。不要违背这些约定。

另一方面,不要觉得把公开数据属性放入对象的接口中不妥,因为如果需要,总能实现读值方法和设值方法,把数据属性变成特性,使用 obj.attr 句法的客户代码不会受到影响。

下图,展示了定义为抽象基类的 Sequence 正式接口

python接口不带ip和端口 python为什么没有接口_python接口不带ip和端口


这里就一目了然了。但不定义完整的接口,也能实现元素的访问、迭代和使用 in 运算符了,例如:

python接口不带ip和端口 python为什么没有接口_python_02


虽然没有 __iter__ 方法,但是 A实例是可迭代的对象,因为发现有 __getitem__ 方法时,Python 会调用它,传入从 0 开始的整数索引,尝试迭代对象(这是一种后备机制)。尽管没有实现 __contains__ 方法,但是 Python 足够智能,能迭代 A实例,因此也能使用 in运算符:Python 会做全面检查,看看有没有指定的元素。

综上,鉴于序列协议的重要性,如果没有 __iter____contains__ 方法,Python 会调用__getitem__ 方法,设法让迭代in 运算符可用。