翻译自Duck Typing

概念

Duck Typing是一种编程方式,其中传递给函数或方法的对象,期望在运行时,该对象满足函数或方法所需对象的所有方法签名和属性。 即只关注行为,而不关注类型

对象的类型本身并不重要。相反,该对象应该支持所有函数或方法在其上调用的所有方法签名/属性。

出于这个原因,Duck typing 有时被视为“一种思考方式而不是一种类型系统”。

在Duck typing中,我们不在函数原型或方法中声明参数类型。 这意味着编译器无法进行类型检查。

真正重要的是这种编程方式认为对象在运行时具有特定的方法/属性。

因此,动态语言通常支持Duck typing。 然而,一些静态语言开始通过结构类型“模仿”这种编程范式。

SAMPLE CODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Source: Wikipedia, 2017.
class Sparrow:
def fly(self):
print("Sparrow flying")
class Airplane:
def fly(self):
print("Airplane flying")
class Whale:
def swim(self):
print("Whale swimming")

# The type of entity is not specified
# We expect entity to have a callable named fly at run time
def lift_off(entity):
entity.fly()

sparrow = Sparrow()
airplane = Airplane()
whale = Whale()

lift_off(sparrow) # prints `Sparrow flying`
lift_off(airplane) # prints `Airplane flying`
lift_off(whale) # Throws the error `'Whale' object has no attribute 'fly'`

讨论

名字由来

如果它像鸭子一样走路,像鸭子一样游泳,像鸭子一样嘎嘎叫,那么它就是一只鸭子。

If it walks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

即像鸭子一样的非鸭子实体也可以被视为鸭子,因为重点在于行为

因此通过类比,只要对象的行为符合预期,对象的类型就不重要了

例如,Book类具有属性numPages和方法getPage(number),其中number是整数。假设函数searchPhrase(book, phrase)用于搜索书中的给定短语。这个函数会调用给定book中的numPages和getPage()。

Newspaper显然不是Book,但是对于Duck typing这不重要。如果Newspaper已经实现了numPages和getPage(number),那么它就可以传递给searchPhrase(book, phrase)。

Duck-typing 优势

img

便捷 > 类型安全

Duck-typing 不是类型系统,它为程序员提供了巨大的灵活性,常见于动态类型语言。 例如,在Python中,许多地方都容易编码。

一般而言,动态类型(dynamic typing)通常会缩短开发时间。 更少的冗余而又不得不写的代码,并且代码也更容易理解。 使用静态类型,编译时的类型检查降低了开发速度,但是在之后使用静态类型可以获得更好的性能。 因此有些人推荐建议Duck-typing仅用于原型设计,而不用于生产。

虽然编译时检查对于尽早发现潜在问题很有用,不过Duck-typing也可以通过强制执行编码约定,文档和测试驱动方法来确保代码的健壮。

Duck typing 和 Late binding

Late binding 是指在运行时将对象或方法绑定到指定的名字上。Duck typing看起来类似于Late binding,但是有一个微妙的差别,Duck typing基于行为而不是声明的类型。

Duck typing 和 Structural typing

img

Duck typing 可以分为静态Duck typing和动态Duck typing,对于后者,跳过编译时检查,这就是一般提及Duck typing时的含义。

静态Duck typing和一些静态类型语言(Scala,F#)支持的Structural typing很相似。 Structural typing允许进行一些编译时检查,注意检测的不是类型,而是支持的方法/属性。 Structural typing可以看作是由编译器保证的Duck typing的子集。 但是,我们还应该注意Structural typing基本上是静态类型,而Duck typing是动态的。

Duck typing 和 implied interfaces

静态类型语言使用显式接口。 在C#中,可以使用interface:任何实现interface的类型都可以传递给接受该接口的方法/函数。 这就是如何将灵活性构建到静态类型语言中,但这需要接口声明。

Python则没有显式接口:隐含接口。 对于Python,隐式接口与Duck typing没有什么不同。

但是,对于C++,在其STL库中支持隐含接口。 鉴于C++进行编译时检查,我们通常不能说隐含的接口与Duck typing相同。 请注意,当接口为隐式时,没有正式的接口定义,一般而言只有接口文档。

Duck typing 与 LBYL, EAFP

LBYL: Look Before You Leap

EAFP: Easy to Ask for Forgiveness than Permission

这是两个相反的概念,都可以用Python来讨论,虽然Python更喜欢EAFP方法。

对于LBYL,我们通常在对象执行操作之前检查对象是否是可兼容类型,例如调用其方法。Python有一个内置函数isinstance()来做到这一点。

对于EAFP,我们只需调用对象的方法,期望该方法存在。如果不存在,则Python解释器将抛出异常,代码应该处理该异常。

在Python的Duck typing中,验证对象的类型的行为很不Pythonic。 因此,尽管可以使用hasattr()来验证对象是否含有某个属性,但Python更喜欢EAFP,这也正好和Duck typing的思想一致。

Reference

DEVOPEDIA Duck Typing