今天,我们来聊一聊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术语表中的鸭子类型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()
都使用的是不同的对象(数字、列表和字符串),并且它们在继承关系中没有联系。只要输入对象支持+
和*
方法,操作就能成功。
执行后输出:
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
)相关的概念,其中关注的是它定义的方法,而不太关注对象的类型或所属类。当您使用鸭子类型时,你无需做检查类型。相反,您要检查给定方法或属性是否存在。
鸭子类型语言为程序员提供了最大的灵活性,程序员只需写最少量的代码。但是这些语言可能并不安全,可能会产生运行时错误,使用时需要格外注意。
参考链接:
- Python术语表:https://docs.python.org/3/glossary.html#term-duck-typing
- 维基百科:https://zh.wikipedia.org/wiki/鸭子类型