【Python】浅谈 鸭子类型 (Duck Typing)

目录

一、来源

二、说明

三、举例

四、不足


一、来源

在程序设计中,鸭子类型 (duck typing) 是动态类型的一种风格。在此风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定的。

事实上,duck typing 的概念源于一个米国诗人的诗句:

" When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck." 

                                                                                                                             —— James Whitcomb Riley

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

                                                                                                                                —— 詹姆斯·惠特科姆·莱利

后来,工程师在 Python 中引入了上述 duck typing 的语言推断测试规则。换言之,它不关注对象的类型,而是关注对象具有的行为 (方法)。以下是 Python 术语表对其定义的阐述:

Pythonic programming style that determines an object's type by inspection of its method or attribute signature rather than by explicit relationship to some type object ("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(). Instead, it typically employs the EAFP (Easier to Ask Forgiveness than Permission) style of programming.


二、说明

鸭子类型 (duck typing) 作为程序设计中的一种 类型推断风格,适用于 大部分脚本语言 / 动态语言 (如 Python、Ruby、Perl、Julia、JavaScript 等) 和 某些静态语言 (如 Golang,通常静态类型语言在编译前便已显式指定变量类型,而 Golang 却则在编译时推断变量类型)。支持 duck typing 的语言,其 解释器/编译器 将会在解释/解析 (Parse) 或编译时推断对象类型。

鸭子测试 可以表述为:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子”。在 duck typing 中,关注的不是对象的类型本身,而是它是如何使用的。与此同时,与 duck typing 相对应的风格是 normal typing,后者由对象类型决定了对象的特性。以下对比说明二者区别:

一方面,以 形象化的表述 为例:在使用 normal typing 的语言中,可以编写一个 “鸭子动作” 函数,它能够接受一个类型为 “鸭子” 的对象,并调用它的 “飞”、“跑” 等方法。在运行前,需要进行对象是否确实为 “鸭子” 等类型检查。相应地,在使用 duck typing 的语言中,也可以编写一个 “鸭子动作” 函数,它能够接受一个任意类型的对象,并调用它的 “飞”、“跑” 等方法。换言之,只要传入对象具有正确的 “飞”、“跑” 等方法,就都可以被函数正常接受和调用。但若这些需要被调用的方法不存在,那么将引发一个运行时错误。

另一方面,以 结合语言的表述 为例:C 属于 normal typing,在 编译阶段进行静态检查,函数定义和传参类型不一致就报错。相应地,Python 属于 duck typing,对象的类型不重要,只要对象具有类型 duck 的方法和属性,那么它就会被当做类型 duck 来使用 (因此经常能够混用类型)。Python 没有静态检查类型匹配情况,只有 运行时找不到相应属性和方法时才报错。 

此外,如果只了解 Java、C++ 等静态语言,可能对鸭子类型的理解并不深刻,因为静态语言中,对象的特性取决于其父类。而动态语言则显著有别,例如 Python 的迭代器 (iterator),任何支持迭代协议 (同时实现 __iter__ 和 __next__ 方法) 的对象都可称之为迭代器,但对象本身是什么类型并不受到限制,甚至可以为任何自定义类。


三、举例

>>> class Duck:
    "Duck 对象具有 fly 方法 (鸭子的飞行行为) 和 run 方法 (鸭子的奔跑行为)"
    def fly(self):
	    print("Duck flying")
	def run(self):
	    print("Duck running")

>>> class Chick:
    "Chick 对象具有 fly 方法 (小鸡的飞行行为) 和 run 方法 (小鸡的奔跑行为)"
    def fly(self):
	    print("Chick flying")
	def run(self):
	    print("Chick running")

>>> class Plane:
    "Plane 对象具有 fly 方法 (飞机的飞行行为)"	
    def fly(self):
	    print("Plane flying")
	
>>> class Fish:
	"Fish 对象具有 swim 方法 (鱼儿的游泳行为)"
     def swim(self):
	    print("FIsh Swimming")
	
>>> def go(entity):    
    "令传入实例对象 entity 执行飞行和奔跑操作 (不论什么东西能飞、能跑就行)"
	entity.fly()  # 令传入实例对象 entity 执行 fly 方法
	entity.run()  # 令传入实例对象 entity 执行 run 方法
	

>>> duck = Duck()  # 实例化 Duck 对象
>>> chick = Chick()  # 实例化 Chick 对象
>>> plane = Plane()  # 实例化 Plane 对象
>>> fish = Fish()  # 实例化 Fish 对象


>>> go(duck)  # 有 fly 和 run 方法就能正常运行, 不关心实例对象的类型是什么 (不论什么东西, 能飞和跑就行)
Duck flying
Duck running

>>> go(chick)  # 有 fly 和 run 方法就能正常运行, 不关心实例对象的类型是什么 (不论什么东西, 能飞和跑就行)
Chick flying
Chick running

>>> go(plane)  # 没有 run 方法就报错, 不关心实例对象的类型是什么 (不论什么东西, 不能跑就不行)
Plane flying
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    go(plane)
  File "<pyshell#13>", line 3, in go
    entity.run()
AttributeError: 'Plane' object has no attribute 'run'

>>> go(fish)  # 没有 fly 方法就报错, 不关心实例对象的类型是什么 (不论什么东西, 不能飞就不行)
Traceback (most recent call last):
  File "<pyshell#44>", line 1, in <module>
    go(fish)
  File "<pyshell#35>", line 2, in go
    entity.fly()
AttributeError: 'Fish' object has no attribute 'fly'

至于各种编程语言中的 duck typing,还可以看这其中的例子:编程语言中的 DUCK TYPING


四、不足

鸭子类型 (duck typing) 没有任何静态检查 (如类型检查、属性检查、方法签名检查等),其通常得益于 不测试方法和函数中参数的类型,而是 依赖文档、清晰的代码和测试来确保正确使用。这既是优点也是缺点,缺点是需要通过文档才能知道参数类型,为了弥补这方面的不足,Python3.6 引入了类型信息,定义变量的时候可以指定类型。此外,从静态类型语言转向动态类型语言的用户通常试图添加一些静态的 (运行前的) 类型检查,从而影响了鸭子类型的益处和可伸缩性,并约束了语言的动态特性。

鸭子类型 (duck typing) 语言的程序 可能会在运行时因不具备某种特定的方法而抛出异常 (例如上例中的 plane 没有 run 方法、fish 没有 fly 方法),形象化地举例说明:如果一只小狗 (对象) 想加入合唱团 (以对象会不会嘎嘎嘎叫的方法为检验标准),也学鸭子那么嘎嘎嘎叫,好吧,它加入了,可是加入之后,却不会像鸭子那样走路,那么,迟早要出问题的。

再举个例子:一只小老鼠被猫盯上了,情急之下,它学了狗叫,猫撤了之后,小老鼠的妈妈不无感叹的对它说:看吧,我让你学的这门儿外语多么重要啊。这虽然是个段子,但是,由于猫在思考时,使用了 "鸭子测试",它以为会叫的就是狗,会对自己产生威胁,所以撤退了,也正是因为这个错误的判断,它误失了一次进食机会。


参考文献

duck type - 简书

Python:动态语言与鸭子类型

编程语言中的 DUCK TYPING - SegmentFault 思否

duck type鸭子类型 - youxin - 博客园

到底啥是鸭子类型(duck typing)带简单例子 - 筱筱的春天 - 博客园

https://baike.baidu.com/item/鸭子类型/10845665?fr=aladdin

  • 12
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值