从鸭子类型看Python面向对象的实现

前言

在实习期间,由于工作需要首次接触了Python这门语言,由于学习和使用的时间非常短,所以当时认为,作为一门解释性语言,在做Web开发方面,Python和PHP的差别不大,甚至在一些应用场景上没有PHP来的简单粗暴。后来,在导师的推荐下,通过《流畅的Python》又一次深入的学习了Python,大致从数据结构、函数、面向对象和控制流程这几个部分深入的学习了这一门语言,对其中作为一等公民的函数和面向对象的实现留下了深刻的映像,开始体会到这门语言的独有魅力。

这本书中,对于Python面向对象的实现机制,介绍了一个非常有趣的概念:鸭子类型(duck typing)。本文将基于鸭子类型这一概念来记录和分享一些Python的学习体会,同时结合过去对Java的学习,比较这两种风格截然不同的语言。同时,基于Java的面向对象,提出鸭子类型背后的两种面向对象思想:多态和范型。

什么是鸭子类型

在维基百科中是这样定义的:

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

而鸭子类型这一名字出自James Whitcomb Riley在鸭子测试中提出的如下的表述:

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

简单归纳就是:对象的类型不再由继承等方式决定,而由实际运行时所表现出的具体行为来决定。

这个概念在解释性语言中还是非常容易理解的,因为解释性语言在定义函数的参数时是无法指定具体参数类型的,另一方面,参数的类型是在解释执行时才能确定,不像类Java语言一样,在编译期编译器就可以确定参数类型,从而在多态模式下确定函数的执行版本了。

另一方面,Python并未沿用Java语言中复杂的接口和类的继承框架,因此对于面向对象有独有的实现风格,接下来的内容和今后更新的博客将详细探讨这个问题。

一个鸭子类型的实例

存在这样一个应用场景:在电子商务系统中,华为旗舰店为了进行商品促销,设计了多套促销方案,而我们需要为该店实现价格计算功能。那么,这个功能可简单的抽象为一个函数:,该函数主要由3个参数构成,用户信息,商品信息,购买数量,同时为了实现促销,商品类还应分别实现两个功能:折扣促发条件和折扣方式。而这时,苹果旗舰店也要进行促销活动,显然苹果旗舰店不是华为旗舰店,但是在鸭子类型的编程模式下,苹果旗舰店只要根据协议,在对应商品类中实现和就可以直接使用我们设计好的价格计算功能了。

哈哈,通过上面的举例描述,相信学过Java或C++的同学一眼就能看出,这不就是多态嘛。利用接口或者超类,将统一的行为进行抽象,再由具体的子类实现进行不同的功能扩展。没错,鸭子类型的编程风格,在实际的应用场景中,确实发挥的是一种面向对象中多态的功能。

Python作为一种解释性语言,相比于PHP的魅力也在这里,实现出一种自己独有的面向对象风格。下一章,将详细介绍Python中的鸭子风格的体现。

Python中的鸭子类型

首先,在Python中,面向对象的多态和抽象是通过把协议当作正式接口来实现的。关于这一点,最明显的特征是对于私密(private)属性Python没有具体的关键字进行支持,也就是说,在Python中,你定义的对象属性始终可以被修改。对此,Python社区只是通过提倡使用一种命名规则来声明私密变量,具体地,就是在变量前加上一个或两个。

因此,我们将在Python的很多特性中,不断看到协议这个概念,但是不同于Java,各种协议是没有具体的接口来进行支持的,甚至在Python中连接口申明的关键字都没有。接下来我们将详细的来讨论Python中的鸭子类型以及有关协议的实现。

a. 序列的实现

基本序列协议:实现和方法

典型的,在Python中,如果希望使用序列的相关功能,例如通过索引进行随机访问,使用一些Python提供的模板方法,例如序列上的排序方法或者是以序列为参数的,最简单的方式都是实现序列协议,采用鸭子类型模式,能够以最低的开发成本使用这些功能。

例如,我们实现了一个简单的纸牌类FrenchDeck,现在需要能够添加洗牌功能,那么只需要让纸牌类实现序列协议即可,示例代码如下:

若不使用序列协议,那么洗牌的功能,将是一个重复造轮子的过程,同时,如果处理不好随机化问题,还会带来新的风险和漏洞。

在这个示例中,纸牌类本身和序列没有任何关系,但是只要实现了序列协议,有了满足序列的相关行为,就成为了序列,因此,就可以正常和安全的使用相关内置方法。

也就是说,对于Python提供的很多以列表为参数的内置方法,我们都可以将实现了列表协议的不同类传递进行去,获对应功能。上面的描述可能有点绕,但是仔细一想,我们似乎又感受到另外一种面向对象的概念,那就是范型。没错,这不就正是范型的定义嘛,我们传递给方法的参数,没有继承相同的父类,甚至没有接口的概念,但是都能得到正确的处理。

b. 切片的实现

基本切片协议:实现方法

说到切片,这可是Python的一种高级使用方法,同时在其他的语言里面基本看不到的一种概念,我们可以通过切片功能,实现列表的灵活应用。

我们发现当实现了列表协议以后,已经能够正常使用分片了。在上述的纸牌例子中,我们已经可以正常使用分片的所有功能。

这里需要注意的是,在这个例子中,由于方法是直接对内置类进行操作,那么可返回正常的对象,但是若操作的是序列本身,则需要进行特殊处理,具体示例如下:

该示例的运行结果如下:

返回结果是array类型而非Vector类型,因此需要对__getitem__方法进行特殊处理,将返回结果统一为Vector类,可通过如下代码进行改进:

c. 其他鸭子类型

Python中,通过对内置特殊函数的实现(前后双_函数),从而获得Python原生的支持,其中对一元中缀表达式的覆盖,对常规加法操作、乘法操作等的覆盖,皆是鸭子类型的表现形式。这些例子还有非常多,至此不再一一列举。

总结

此篇文章以鸭子类型这一编程风格为中心,探讨了Python的面向对象的部分特性,其中对鸭子类型的本质进行了分析和解释,更多的是自己对Python面向对象的一些理解。

同时,结合博主过去对Java面向对象的理解,讨论了Python面向对象的两大重要概念:多态和范型。同时结合《流畅的Python》中提供的两大经典示例进行了概念的分析。从而加深了对鸭子类型这一编程风格的理解,如若有不正之处,望各位能指出~

最后,今后本公众号将继续更新:作为一等公民的函数、控制流程和元编程等章节,望与各位Python学习者一同成长,还望各位继续关注本公众号。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181210G1GW7P00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券