- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- 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.10 参数化装饰器
解析源码中的装饰器时,Python 把被装饰的函数作为第一个参数传给装饰器函数。那怎么让装饰器接受其他参数呢?答案是:创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。不明白什么意思?当然。下面以我们见过的最简单的装饰器为例说明:示例 7-22 中的 register。
示例 7-22 示例 7-2 中 registration.py 模块的删减版,这里再次给出是为了便于讲解
registry = [] def register(func): print('running register(%s)' % func) registry.append(func) return func @register def f1(): print('running f1()') print('running main()') print('registry ->', registry) f1()
7.10.1 一个参数化的注册装饰器
为了便于启用或禁用 register 执行的函数注册功能,我们为它提供一个可选的 active 参数,设为 False 时,不注册被装饰的函数。实现方式参见示例 7-23。从概念上看,这个新的 register 函数不是装饰器,而是装饰器工厂函数。调用它会返回真正的装饰器,这才是应用到目标函数上的装饰器。
示例 7-23 为了接受参数,新的 register 装饰器必须作为函数调用
registry = set() ➊ def register(active=True): ➋ def decorate(func): ➌ print('running register(active=%s)->decorate(%s)' % (active, func)) if active: ➍ registry.add(func) else: registry.discard(func) ➎ return func ➏ return decorate ➐ @register(active=False) ➑ def f1(): print('running f1()') @register() ➒ def f2(): print('running f2()') def f3(): print('running f3()')
❶ registry 现在是一个 set 对象,这样添加和删除函数的速度更快。
❷ register 接受一个可选的关键字参数。
❸ decorate 这个内部函数是真正的装饰器;注意,它的参数是一个函数。
❹ 只有 active 参数的值(从闭包中获取)是 True 时才注册 func。
❺ 如果 active 不为真,而且 func 在 registry 中,那么把它删除。
❻ decorate 是装饰器,必须返回一个函数。
❼ register 是装饰器工厂函数,因此返回 decorate。
❽ @register 工厂函数必须作为函数调用,并且传入所需的参数。
❾ 即使不传入参数,register 也必须作为函数调用(@register()),即要返回真正的装饰器 decorate。
这里的关键是,register() 要返回 decorate,然后把它应用到被装饰的函数上。
示例 7-23 中的代码在 registration_param.py 模块中。如果导入,得到的结果如下:
>>> import registration_param running register(active=False)->decorate(<function f1 at 0x10063c1e0>) running register(active=True)->decorate(<function f2 at 0x10063c268>) >>> registration_param.registry {<function f2 at 0x10063c268>}
注意,只有 f2 函数在 registry 中;f1 不在其中,因为传给 register 装饰器工厂函数的参数是 active=False,所以应用到 f1 上的 decorate 没有把它添加到 registry 中。
如果不使用 @ 句法,那就要像常规函数那样使用 register;若想把 f 添加到 registry 中,则装饰 f 函数的句法是 register()(f);不想添加(或把它删除)的话,句法是 register(active=False)(f)。示例 7-24 演示了如何把函数添加到 registry 中,以及如何从中删除函数。
示例 7-24 使用示例 7-23 中的 registration_param 模块
>>> from registration_param import * running register(active=False)->decorate(<function f1 at 0x10073c1e0>) running register(active=True)->decorate(<function f2 at 0x10073c268>) >>> registry # ➊ {<function f2 at 0x10073c268>} >>> register()(f3) # ➋ running register(active=True)->decorate(<function f3 at 0x10073c158>) <function f3 at 0x10073c158> >>> registry # ➌ {<function f3 at 0x10073c158>, <function f2 at 0x10073c268>} >>> register(active=False)(f2) # ➍ running register(active=False)->decorate(<function f2 at 0x10073c268>) <function f2 at 0x10073c268> >>> registry # ➎ {<function f3 at 0x10073c158>}
❶ 导入这个模块时,f2 在 registry 中。
❷ register() 表达式返回 decorate,然后把它应用到 f3 上。
❸ 前一行把 f3 添加到 registry 中。
❹ 这次调用从 registry 中删除 f2。
❺ 确认 registry 中只有 f3。
参数化装饰器的原理相当复杂,我们刚刚讨论的那个比大多数都简单。参数化装饰器通常会把被装饰的函数替换掉,而且结构上需要多一层嵌套。接下来会探讨这种函数金字塔。
7.10.2 参数化clock装饰器
本节再次探讨 clock 装饰器,为它添加一个功能:让用户传入一个格式字符串,控制被装饰函数的输出。参见示例 7-25。
为了简单起见,示例 7-25 基于示例 7-15 中最初实现的 clock,而不是示例 7-17 中使用 @functools.wraps 改进后的版本,因为那一版增加了一层函数。
示例 7-25 clockdeco_param.py 模块:参数化 clock 装饰器
import time DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}' def clock(fmt=DEFAULT_FMT): ➊ def decorate(func): ➋ def clocked(*_args): ➌ t0 = time.time() _result = func(*_args) ➍ elapsed = time.time() - t0 name = func.__name__ args = ', '.join(repr(arg) for arg in _args) ➎ result = repr(_result) ➏ print(fmt.format(**locals())) ➐ return _result ➑ return clocked ➒ return decorate ➓ if __name__ == '__main__': @clock() def snooze(seconds): time.sleep(seconds) for i in range(3): snooze(.123)
❶ clock 是参数化装饰器工厂函数。
❷ decorate 是真正的装饰器。
❸ clocked 包装被装饰的函数。
❹ _result 是被装饰的函数返回的真正结果。
❺ _args 是 clocked 的参数,args 是用于显示的字符串。
❻ result 是 _result 的字符串表示形式,用于显示。
❼ 这里使用 **locals() 是为了在 fmt 中引用 clocked 的局部变量。
❽ clocked 会取代被装饰的函数,因此它应该返回被装饰的函数返回的值。
❾ decorate 返回 clocked。
❿ clock 返回 decorate。
⓫ 在这个模块中测试,不传入参数调用 clock(),因此应用的装饰器使用默认的格式 str。
在 shell 中运行示例 7-25,会得到下述结果:
$ python3 clockdeco_param.py [0.12412500s] snooze(0.123) -> None [0.12411904s] snooze(0.123) -> None [0.12410498s] snooze(0.123) -> None
示例 7-26 和示例 7-27 是另外两个模块,它们使用了 clockdeco_param 模块中的新功能,随后是两个模块输出的结果。
示例 7-26 clockdeco_param_demo1.py
import time from clockdeco_param import clock @clock('{name}: {elapsed}s') def snooze(seconds): time.sleep(seconds) for i in range(3): snooze(.123)
示例 7-26 的输出:
$ python3 clockdeco_param_demo1.py snooze: 0.12414693832397461s snooze: 0.1241159439086914s snooze: 0.12412118911743164s
示例 7-27 clockdeco_param_demo2.py
import time from clockdeco_param import clock @clock('{name}({args}) dt={elapsed:0.3f}s') def snooze(seconds): time.sleep(seconds) for i in range(3): snooze(.123)
示例 7-27 的输出:
$ python3 clockdeco_param_demo2.py snooze(0.123) dt=0.124s snooze(0.123) dt=0.124s snooze(0.123) dt=0.124s
受本书篇幅限制,我们对装饰器的探讨到此结束。延伸阅读中的资料讨论了构建工业级装饰器的技术,尤其是 Graham Dumpleton 的博客和 wrapt 模块。
Graham Dumpleton 和 Lennart Regebro(本书的技术审校之一)认为,装饰器最好通过实现 __call__ 方法的类实现,不应该像本章的示例那样通过函数实现。我同意使用他们建议的方式实现非平凡的装饰器更好,但是使用函数解说这个语言特性的基本思想更易于理解。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论