你也许见过很多人对于Python的评价,他们说Python是“脚本语言”和“胶水语言”,在某种程度上,他们说的是对的。但是,如果你学习过Python,你会知道Python也支持面向对象的编程,更有甚者,在Python中所有东西都是对象。
事实上,Python有着很强大的支持面向对象编程的能力,比如我们刚介绍过的pathlib模块,它就是一个用面向对象思想来处理文件系统的模块。
可以说,Python是“能屈能伸”吧,小打小闹的时候开箱即用轻松上手,认真严肃起来耍大刀也是虎虎生风不遑多让。
只不过,面向对象就一定是好事么?支持面向对象编程,就一定要时刻这样用么?
下面段落出自《Python最佳实践指南》,这是由圈内大神Kenneth Reitz发起和维护的开源项目(文末附了相关链接),让我们一起来看看K神提出的建议。
Kenneth Reitz大神的建议
Python 有时被描述为一种面向对象的编程语言。这可能对大家有些误导,需要加以澄清。
在 Python 中,所有东西都视为一个对象,并且可以按对象处理。当我们说,函数是“一级”对象,就是将函数视为对象的意思。函数、类、字符串,甚至类型都是 Python 中的对象:像任何对象一样,它们有一个类型,可以作为函数参数传递,并且它们可能有方法和属性。按这种理解, Python 是一种面向对象的语言。
但是,与 Java 不同, Python 并没有将面向对象的编程作为主要的编程范例来实施。Python 项目不采用面向对象的方式是完全可行的,即不使用或很少使用类定义、类继承或特定于面向对象编程的任何其他机制。
此外,从 模块 部分可以看出, Python 处理模块和名称空间的方式为开发人员提供了一种自然的方法来确保抽象层的封装和分离,这两者都是使用面向对象的最常见原因。因此,当业务模型不需要面向对象时, Python 程序员有更大的自由来不使用面向对象编程。
基于一些因素的考虑,我们应避免不必要的面向对象编程。 当我们想将一些状态和功能粘合在一起时,定义自定义类是很有用的。在函数编程的讨论中,我们指出,“不必要的面向对象编程”这个问题出自方程的“状态”部分。
在某些体系结构中,例如典型的 web 应用程序,会生成多个 Python 进程实例,以响应可能同时发生的外部请求。在这种情况下,将一些状态保存到实例对象中,意味着保留一些关于世界的静态信息,这很容易出现并发或竞争问题。有时,在对象的初始化(通常用 __init__()
方法来完成)状态和实际使用对象方法的状态之间,世界信息可能已经改变,保持的状态可能已经过时。例如,一个请求加载了内存中的某一项,并将其标记为由用户读取。而另一个请求同时要求删除该项,这可能发生在第一个进程加载该项之后,然后我们必须将其标记为已删除对象。
上述以及其他问题引出了这样的想法:使用无状态函数是一种更好的编程范例。
另一种说法是建议尽可能少的使用具有隐式上下文和副作用的函数和程序。函数的隐式上下文由全局变量和持久层中的数据项(使用方法访问)组成。副作用是指函数对其隐式上下文所做的更改。如果函数会保存或删除全局变量或持久层中的数据,则称它有副作用。
将有上下文和副作用的函数与逻辑函数(称为纯函数)隔离开来,可以获得以下好处:
纯函数是确定性的:给定一个固定的输入,输出始终是相同的。
纯函数需要重构或优化时,更容易更改或替换。
纯函数更易于使用单元测试进行测试:对于复杂的上下文设置和事后的数据清理的需求更少。
纯函数更容易操作、修饰和传递。
总之,针对某些体系结构,由于没有上下文或副作用,纯函数是比类和对象更有效的构建块。
显然,面向对象编程在许多情况下是有用的,甚至是必要的,例如在开发图形化桌面应用程序或游戏时,被操作的东西(窗口、按钮、化身、车辆)在计算机内存中具有相对较长的寿命。
猫猫的思考
以上就是K神的建议。他在后半段提到了纯函数(pure functions),这让猫猫联想到了函数式编程(Functional Programming),但纯函数似乎是一种更具普遍性的东西,它就像是一种数学上的定义。纯函数真的有那么神么?
于是,猫猫去google了“纯函数”。没想到,排在前面的结果竟然全跟Javascript相关。
除去维基百科的条目,第一个答案指向了一本GitBook《JS函数式编程指南》,好奇的猫猫点进去看了,结果大为叹服!建议大家有条件的话都去读一下(链接见文末,不懂js也不影响理解)。
非常巧合的是,这本书的作者也发表了他对于面向对象编程的看法:
我最喜欢的名言之一是 Erlang 语言的作者 Joe Armstrong 说的这句话:“面向对象语言的问题是,它们永远都要随身携带那些隐式的环境。你只需要一个香蕉,但却得到一个拿着香蕉的大猩猩…以及整个丛林”。
读完《纯函数的好处》章节,猫猫提炼了几条笔记。一方面是为了加强对纯函数的理解,在实战中规避一些“不纯”的用法,另一方面,也提出了几个思考和疑问,今后在学习Python的过程中,留神找到答案:
1、避免使用不纯的函数。JS中的splice是个不纯的函数,那Python中是否也有这样的函数呢?
2、下例中第一个是不纯的,因为函数的结果取决于minimum这个可变变量,换句话说,它取决于系统状态(system state);这一点令人沮丧,因为它引入了外部的环境,从而增加了认知负荷(cognitive load)。(题外话:这个例子,猫猫大有感触。公司有个项目的老版本代码中,充斥了各种全局变量,小伙伴们在维护时吃了好多苦头!)JS中可以用Object.freeze 方法令minimum成为不可变对象,Python中有类似的实现么?
// 不纯的
var minimum = 21;
var checkAge = function(age) {
return age >= minimum;
};
// 纯的
var checkAge = function(age) {
var minimum = 21;
return age >= minimum;
};
3、不纯函数会带来“副作用”,其“作用”本身没有坏处,但其“副”是滋生bug的温床。并不是说,要禁止使用一切副作用,而是说,要让它们在可控的范围内发生。坚持这种「相同输入得到相同输出」的原则。
4、纯函数实际上就是数学定义中的函数。“函数是不同数值之间的特殊关系:每一个输入值返回且只返回一个输出值。”
5、追求“纯”的理由:可缓存性(有点像生成器,延迟执行)、可移植性/自文档化(因其完全自给自足,依赖关系明确)、可测试性(为函数式环境定制的测试工具,JS中有Quickcheck,Python中有么?)、合理性(引用透明性:一段代码可以替换成它执行所得的结果,而且是在不改变整个程序行为的前提下替换)、并行代码(纯函数根本不需要访问共享的内存,而且纯函数也不会因副作用而进入竞争态(race condition))。
今天的分享就到这了,最后再逻辑几句。本篇文章里,一句Python代码都没有,真的是够“干”的了。猫猫有时候挺喜欢看这样的文章,因为它会带给你思想上的启迪,就像是绝世高人在传授秘籍心法一样。所以,猫猫也喜欢转述和思考这类问题,比如之前发过的一篇《超强汇总:学习Python列表,只需这篇文章就够了》,就不仅仅有代码层面的内容,还特意加入了Guido老爹关于Python列表索引为何从0开始的解释,以及其它编程语言对索引值的考虑。
不记得在哪里曾看到过一句话,送予大家共勉:
如果一个人眼里只看得见代码,那他跟咸(ma)鱼(nong)有啥区别?