接下来的两章继续探讨使用函数对象编程。第 6 章说明一等函数如何简化某些经典的面向对象设计模式,第 7 章说明函数装饰器(一种特别的高阶函数)和支持装饰器的闭包机制。

《Python Cookbook(第 3 版)中文版》(David Beazley 和 Brian K. Jones 著)的第 7 章是对本章和第 7 章很好的补充,那一章基本上使用不同的方式探讨了相同的概念。

Python 语言参考手册中的“3.2. The standard type hierarchy”一节(https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy)对 7 种可调用类型和其他所有内置类型做了介绍。

本章讨论的 Python 3 专有特性有各自的 PEP:“PEP 3102—Keyword-Only Arguments”(https://www.python.org/dev/peps/pep-3102/)和“PEP 3107—Function Annotations”(https://www.python.org/dev/peps/pep-3107/)。

若想进一步了解目前对注解的使用,Stack Overflow 网站中有两个问答值得一读:一个是“What are good uses for Python3's‘Function Annotations’”(http://stackoverflow.com/questions/3038033/what-are-good-uses-for-python3s-function-annotations),Raymond Hettinger 给出了务实的回答和深入的见解;另一个是“What good are Python function annotations?”(http://stackoverflow.com/questions/13784713/what-good-are-python-function-annotations),某 个回答中大量引用了 Guido van Rossum 的观点。

如果你想使用 inspect 模块,“PEP 362—Function Signature Object”(https://www.python.org/dev/peps/pep-0362/)值得一读,可以帮助了解实现细节。

A. M. Kuchling 的文章“Python Functional Programming HOWTO”(http://docs.python.org/3/howto/functional.html)对 Python 函数式编程做了很好的介绍。不过,那篇文章的重点是使用迭代器和生成器,这是第 14 章的话题。

fn.pyhttps://github.com/kachayev/fn.py)是为 Python 2 和 Python 3 提供函数式编程支持的包。据作者 Alexey Kachayev 介绍,fn.py 提供了 Python“所缺少的函数式特性”。这个包提供的 @recur.tco 装饰器为 Python 中的无限递归实现了尾调用优化。此外,fn.py 还提供了很多其他函数、数据结构和诀窍。

Stack Overflow 网站中的问题“Python: Why is functools.partial necessary?”(http://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary)有个详实(而有趣)的回答,答主是 Alex Martelli,他是经典的《Python 技术手册》一书的作者。

Jim Fulton 开发的 Bobo 或许是第一个称得上是面向对象的 Web 框架。如果你对这个框架感兴趣,想进一步学习它最近的重写版本,先从“Introduction”(http://bobo.readthedocs.io/en/latest/)入手。在 Joel Spolsky 的博客中,Phillip J. Eby 在评论中提到了 Bobo 的一些早期历史(http://discuss.fogcreek.com/joelonsoftware/default.asp?cmd=show&ixPost=94006)。

杂谈

关于Bobo

我的 Python 编程生涯从 Bobo 开始。1998 年,我在自己的第一个 Python Web 项目中使用了 Bobo。当时我在寻找编写 Web 应用的面向对象方式,尝试过一些 Perl 和 Java框架之后,我发现了 Bobo。

1997 年,Bobo 开创了对象发布概念:直接把 URL 映射到对象层次结构上,无需配置路由。看到这种做法的精妙之处后,我被 Bobo 吸引住了。Bobo 还能通过分析处理请求的方法或函数的签名来自动处理 HTTP 查询。

Bobo 由 Jim Fulton 创建,他被人称为“Zope 教皇”(The Zope Pope),因为他在 Zope 框架的开发中的起到领衔作用。Zope 是 Plone CMS、SchoolTool、ERP5 和其他大型 Python 项目的基础。Jim 还是 ZODB(Zope Object Database)的创建者,这是一个事务型对象数据库,提供了 ACID(“atomicity, consistency, isolation, and durability”,原子性、一致性、隔离性和耐久性),它的设计目的是简化 Python 的使用。

后来,为了支持 WSGI 和现代的 Python 版本(包括 Python 3),Jim 从头重写了 Bobo。写作本书时,Bobo 使用 six 库做函数内省,这是为了兼容 Python 2 和 Python 3,因为这两个版本在函数对象和相关的 API 上做了修改。

Python 是函数式语言吗

2000 年左右,我在美国做培训,Guido van Rossum 到访了教室(他不是讲师)。在课后的问答环节,有人问他 Python 的哪些特性是从其他语言借鉴而来的。他答道:“Python 中一切好的特性都是从其他语言中借鉴来的。”

布朗大学的计算机科学教授 Shriram Krishnamurthi 在其论文“Teaching Programming Languages in a Post-Linnaean Age”(http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/)的开头这样写道:

编程语言“范式”已近末日,它们是旧时代的遗留物,令人厌烦。既然现代语言的设计者对范式不屑一顾,那么我们的课程为什么要像奴隶一样对其言听计从?

在那篇论文中,下面这一段点名提到了 Python:

对 Python、Ruby 或 Perl 这些语言还要了解什么呢?它们的设计者没有耐心去精确实现林奈层次结构;设计者按照自己的意愿从别处借鉴特性,创建出完全无视过往概念的大杂烩。

Krishnamurthi 指出,不要试图把语言归为某一类;相反,把它们视作特性的聚合更有用。

为 Python 提供一等函数打开了函数式编程的大门,不过这并不是 Guido 的目的。他在“Origins of Python's Functional Features”一文(http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html)中说,mapfilterreduce 的最初目的是为 Python 增加 lambda 表达式。这些特性都由 Amrit Prem 贡献,添加在 1994 年发布的 Python 1.0 中(参见 CPython 源码中的 Misc/HISTORY 文件,https://hg.python.org/cpython/file/default/Misc/HISTORY)。

lambdamapfilterreduce 首次出现在 Lisp 中,这是最早的一门函数式语言。然而,Lisp 没有限制在 lambda 表达式中能做什么,因为 Lisp 中的一切都是表达式。 Python 使用的是面向语句的句法,表达式中不能包含语句,而很多语言结构都是语句,例如 try/catch,我编写 lambda 表达式时最想念这个语句。Python 为了提高句法的可读性,必须付出这样的代价。6Lisp 有很多优点,可读性一定不是其中之一。

讽刺的是,从另一门函数式语言(Haskell)中借用列表推导之后,Python 对 mapfilter,以及 lambda 表达式的需求极大地减少了。

除了匿名函数句法上的限制之外,影响函数式编程惯用法在 Python 中广泛使用的最大障碍是缺少尾递归消除(tail-recursion elimination),这是一项优化措施,在函数的定义体“末尾”递归调用,从而提高计算函数的内存使用效率。Guido 在另一篇博客文章(“Tail Recursion Elimination”,http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html)中解释了为什么这种优化措施不适合 Python。这篇文章详细讨论了技术论证,不过前三个也是最重要的原因与易用性有关。Python 作为一门易于使用、学习和教授的语言并非偶然,有 Guido 在为我们把关。

综上,从设计上看,不管函数式语言的定义如何,Python 都不是一门函数式语言。Python 只是从函数式语言中借鉴了一些好的想法。

匿名函数的问题

除了 Python 独有的句法上的局限,在任何一门语言中,匿名函数都有一个严重的缺点:没有名称。

我是半开玩笑的。函数有名称,栈跟踪更易于阅读。匿名函数是一种便利的简洁方式,人们乐于使用它们,但是有时会忘乎所以,尤其是在鼓励深层嵌套匿名函数的语言和环境中,如 JavaScript 和 Node.js。匿名函数嵌套的层级太深,不利于调试和处理错误。Python 中的异步编程结构更好,或许就是因为 lambda 表达式有局限。我保证,后面会进一步讨论异步编程,但是必须等到第 18 章。顺便说一下,promise 对象、期物(future)和 deferred 对象是现代异步 API 中使用的概念。把它们与协程结合起来,能避免掉入“回调地狱”。18.5 节会说明如何不用回调来做异步编程。

6此外,还有一个问题:把代码粘贴到 Web 论坛时,缩进会丢失。当然,这是题外话。