- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
7.12 延伸阅读
《Python Cookbook(第 3 版)中文版》(David Beazley 和 Brian K. Jones 著)的第 9 章“元编程”有几个诀窍构建了基本的装饰器和特别复杂的装饰器。其中,“9.6 定义一个能接收可选参数的装饰器”一节中的装饰器可以作为常规的装饰器调用,也可以作为装饰器工厂函数调用,例如 @clock 或 @clock()。
Graham Dumpleton 写了一系列博客文章,深入剖析了如何实现行为良好的装饰器,第一篇是“How You Implemented Your Python Decorator is Wrong”。他在这方面的深厚知识充分体现在在他编写的 wrapt 模块中。这个模块的作用是简化装饰器和动态函数包装器的实现,即使多层装饰也支持内省,而且行为正确,既可以应用到方法上,也可以作为描述符使用。(描述符在本书第 20 章讨论。)
Michele Simionato 开发了一个包,根据文档,它旨在“简化普通程序员使用装饰器的方式,并且通过各种复杂的示例推广装饰器”。这个包是 decorator,可通过 PyPI 安装。
Python Decorator Library 维基页面在 Python 刚添加装饰器这个特性时就创建了,里面有很多示例。由于那个页面是几年前开始编写的,有些技术已经过时了,不过仍是很棒的灵感来源。
PEP 443对单分派泛函数的基本原理和细节做了说明。Guido van Rossum 很久以前(2005 年 3 月)写的一篇博客文章“Five-Minute Multimethods in Python”详细说明了如何使用装饰器实现泛函数(也叫多方法)。他给出的代码支持多分派(即根据多个定位参数进行分派)。Guido 写的多方法代码很棒,但那只是教学示例。如果想使用现代的技术实现多分派泛函数,并支持在生产环境中使用,可以用 Martijn Faassen 开发的 Reg。Martijn 还是模型驱动型 REST 式 Web 框架 Morepath的开发者。
Fredrik Lundh 写的一篇短文“Closures in Python”解说了闭包这个术语。
“PEP 3104—Access to Names in Outer Scopes”说明了引入 nonlocal 声明的原因:重新绑定既不在本地作用域中也不在全局作用域中的名称。这份 PEP 还概述了其他动态语言(Perl、Ruby、JavaScript,等等)解决这个问题的方式,以及 Python 中可用设计方案的优缺点。
“PEP 227—Statically Nested Scopes”更偏重于理论,说明了 Python 2.1 引入的词法作用域。词法作用域在这一版里是一种方案,到 Python 2.2 就变成了标准。此外,这份 PEP 还说明了 Python 中闭包的基本原理和实现方式的选择。
杂谈
任何把函数当作一等对象的语言,它的设计者都要面对一个问题:作为一等对象的函数在某个作用域中定义,但是可能会在其他作用域中调用。问题是,如何计算自由变量?首先出现的最简单的处理方式是使用“动态作用域”。也就是说,根据函数调用所在的环境计算自由变量。
如果 Python 使用动态作用域,不支持闭包,那么 avg(与示例 7-9 类似)可以写成这样:
>>> ### 这不是真实的Python控制台会话! ### >>> avg = make_averager() >>> series = [] # ➊ >>> avg(10) 10.0 >>> avg(11) # ➋ 10.5 >>> avg(12) 11.0 >>> series = [1] # ➌ >>> avg(5) 3.0
❶ 使用 avg 之前要自己定义 series = [],因此我们必须知道 averager(在 make_averager 内部)引用的是一个列表。
❷ 在背后使用 series 累计要计入平均值的值。
❸ 执行 series = [1] 后,之前的列表消失了。同时计算两个独立的移动平均值时可能会发生这种意外。
函数应该是黑盒,把实现隐藏起来,不让用户知道。但是对动态作用域来说,如果函数使用自由变量,程序员必须知道函数的内部细节,这样才能搭建正确运行所需的环境。
另一方面,动态作用域易于实现,这可能就是 John McCarthy 创建 Lisp(第一门把函数视作一等对象的语言)时采用这种方式的原因。Paul Graham 写的“The Roots of Lisp”一文对 John McCarthy 关于 Lisp 语言那篇论文(“Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I”)做了通俗易懂的解说。McCarthy 那篇论文是和贝多芬第九交响曲一样伟大的杰作。Paul Graham 使用通俗易懂的语言翻译了那篇论文,把数学原理转换成了英语和可运行的代码。
Paul Graham 的注解还指出动态作用域难以实现。下面这段文字引自“The Roots of Lisp”一文:
就连第一个 Lisp 高阶函数示例都因为动态作用域而无法运行,这充分证明了动态作用域的危险性。McCarthy 在 1960 年可能没有全面认识到动态作用域的影响。动态作用域在各种 Lisp 实现中存在的时间特别长,直到 Sussman 和 Steele 在 1975 年开发出 Scheme 为止。词法作用域不会把 eval 的定义变得多么复杂,只是编译器可能更难编写。
如今,词法作用域已成常态:根据定义函数的环境计算自由变量。词法作用域让人更难实现支持一等函数的语言,因为需要支持闭包。不过,词法作用域让代码更易于阅读。Algol 之后出现的语言大都使用词法作用域。
多年来,Python 的 lambda 表达式不支持闭包,因此在博客圈的函数式编程极客群体中,这个特性的名声并不好。Python 2.2(2001 年 12 月发布)修正了这个问题,但是博客圈的固有印象不会轻易转变。自此之后,仅仅由于句法上的局限,lambda 一直处于尴尬的境地。
Python 装饰器和装饰器设计模式
Python 函数装饰器符合 Gamma 等人在《设计模式:可复用面向对象软件的基础》一书中对“装饰器”模式的一般描述:“动态地给一个对象添加一些额外的职责。就扩展功能而言,装饰器模式比子类化更灵活。”
在实现层面,Python 装饰器与“装饰器”设计模式不同,但是有些相似之处。
在设计模式中,Decorator 和 Component 是抽象类。为了给具体组件添加行为,具体装饰器的实例要包装具体组件的实例。《设计模式:可复用面向对象软件的基础》一书是这样说的:
装饰器与它所装饰的组件接口一致,因此它对使用该组件的客户透明。它将客户请求转发给该组件,并且可能在转发前后执行一些额外的操作(例如绘制一个边框)。透明性使得你可以递归嵌套多个装饰器,从而可以添加任意多的功能。(第 115 页)
在 Python 中,装饰器函数相当于 Decorator 的具体子类,而装饰器返回的内部函数相当于装饰器实例。返回的函数包装了被装饰的函数,这相当于“装饰器”设计模式中的组件。返回的函数是透明的,因为它接受相同的参数,符合组件的接口。返回的函数把调用转发给组件,可以在转发前后执行额外的操作。因此,前面引用那段话的最后一句可以改成:“透明性使得你可以递归嵌套多个装饰器,从而可以添加任意多的行为。”这就是叠放装饰器的理论基础。
注意,我不是建议在 Python 程序中使用函数装饰器实现“装饰器”模式。在特定情况下确实可以这么做,但是一般来说,实现“装饰器”模式时最好使用类表示装饰器和要包装的组件。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论