Python中的鸭子类型

今天,我们来聊一聊Python中的鸭子类型(duck typing)。
编程语言具有类型概念,例如Python中有数字类型、字符串类型、布尔类型,或者更加复杂的结构,例如元组tuple、列表list、集合set和字典类型dict等等。
根据如何将类型解析并赋值给各种构造(例如变量,表达式,函数,函数参数等),编程语言可以归类为“鸭子类型”,“结构化类型”或“标称类型”。
本质上,分类决定了对象如何被解析并推断为具体的类型。
鸭子测试 鸭子类型(duck typing)语言使用鸭子测试来评估对象是否可以被解析为特定的类型。Python就是其中一种。
这个概念的名字来源于由詹姆斯·惠特科姆·莱利提出的鸭子测试。“鸭子测试”可以这样表述:

“如果看起来像鸭子,叫起来像鸭子,那么它一定是鸭子。”
If it looks like a duck and quacks like a duck, it must be a duck.
James Whitcomb Riley
Python中的鸭子类型
文章图片




鸭子类型 鸭子类型在程序设计中是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。
在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的"走"和"叫"方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的"走"和"叫"方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
鸭子类型通常得益于"不"测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。
在常规类型中,我们能否在一个特定场景中使用某个对象取决于这个对象的类型,而在鸭子类型中,则取决于这个对象是否具有某种属性或者方法——即只要具备特定的属性或方法,能通过鸭子测试,就可以使用。



Python术语 Python术语表中的鸭子类型duck-typing
A programming style which does not look at an object’s type to determine if it has the right interface; instead, the method or attribute is simply called or used (“If it looks like a duck and quacks like a duck, it must be a duck.”) By emphasizing interfaces rather than specific types, well-designed code improves its flexibility by allowing polymorphic substitution. Duck-typing avoids tests using type() or isinstance(). (Note, however, that duck-typing can be complemented with abstract base classes.) Instead, it typically employs hasattr() tests or EAFP programming.
译文:
鸭子类型是一种编程风格,决定一个对象是否有正确的接口,关注点在于它的方法或属性,而不是它的类型(“如果它看起来像鸭子,像鸭子一样嘎嘎叫,那么它一定是鸭子。”)。通过强调接口而不是特定类型,设计良好的代码通过多态提高了灵活性。鸭子类型无需使用 type()isinstance() 进行检查(注意,鸭子类型可以用抽象基类来补充),相反,它通常使用 hasattr()来检查,或是 EAFP 编程。



代码示例 举个例子:
# 定义一个计算函数,接收3个入参 def calculate(a, b, c): return (a + b) * c# 分别计算3种情况的结果 result1 = calculate(1, 2, 3) result2 = calculate([1, 2, 3], [4, 5, 6], 2) result3 = calculate("打工人", "打工魂", 3)# 打印3种结果 print(result1, result2, result3, sep='\n')

代码解释:在上面的例子中,我们每次调用calculate()都使用的是不同的对象(数字、列表和字符串),并且它们在继承关系中没有联系。只要输入对象支持+*方法,操作就能成功。
【Python中的鸭子类型】执行后输出:
9 [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6] 打工人打工魂打工人打工魂打工人打工魂

这样,鸭子类型在不使用继承的情况下使用了多态。
再举个栗子:
# 鸭子类 class Duck:def quack(self): print("这鸭子正在嘎嘎叫")def feathers(self): print("这鸭子拥有白色和灰色的羽毛")# 人类 class Person:def quack(self): print("这人正在模仿鸭子")def feathers(self): print("这人在地上拿起1根羽毛然后给其他人看")# 函数/接口 def in_the_forest(duck): duck.quack() duck.feathers()if __name__ == '__main__':donald = Duck()# 创建一个Duck类的实例 john = Person()# 创建一个Person类的实例in_the_forest(donald)# 调用函数,传入Duck的实例 in_the_forest(john)# 调用函数,传入Person的实例

代码运行后输出:
这鸭子正在嘎嘎叫 这鸭子拥有白色和灰色的羽毛 这人正在模仿鸭子 这人在地上拿起1根羽毛然后给其他人看





总结 鸭子类型是一个与动态类型(dynamic typing)相关的概念,其中关注的是它定义的方法,而不太关注对象的类型或所属类。当您使用鸭子类型时,你无需做检查类型。相反,您要检查给定方法或属性是否存在。
鸭子类型语言为程序员提供了最大的灵活性,程序员只需写最少量的代码。但是这些语言可能并不安全,可能会产生运行时错误,使用时需要格外注意。




参考链接:
  1. Python术语表:https://docs.python.org/3/glossary.html#term-duck-typing
  2. 维基百科:https://zh.wikipedia.org/wiki/鸭子类型

    推荐阅读