# 《流畅的Python》读书笔记 # 第7章 函数装饰器和闭包 # 函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为。这是一项强大的功能,但是若想掌握,必须理解闭包。 # nonlocal 是新近出现的保留关键字,在 Python 3.0 中引入。 # 除了在装饰器中有用处之外,闭包还是回调式异步编程和函数式编程风格的基础。 # 本章的最终目标是解释清楚函数装饰器的工作原理,包括最简单的注册装饰器和较复杂的参数化装饰器。但是,在实现这一目标之前,我们要讨论下述话题: #Python 如何计算装饰器句法 #Python 如何判断变量是不是局部的 #闭包存在的原因和工作原理 #nonlocal 能解决什么问题 # 掌握这些基础知识后,我们可以进一步探讨装饰器: #实现行为良好的装饰器 #标准库中有用的装饰器 #实现一个参数化装饰器 # 7.1 装饰器基础知识 # 装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。 # 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。 # 示例 7-1 装饰器通常把函数替换成另一个函数 def deco(func): def inner(): print('running inner()') return inner @deco def target(): print('running target()') print(target()) print(target) # 7.2 Python何时执行装饰器 #装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。 # 示例 7-2 registration.py 模块 registry=[] def register(func): print('running register(%s)'%func) registry.append(func) return func @register def f1(): print('running f1()') @register def f2(): print('running f2()') def f3(): print('running f3()') def main(): print('running main()') print('registry ->',registry) f1() f2() f3() if __name__=='__main__': main() # running register(<function f1 at 0x016F6540>) # running register(<function f2 at 0x016F64F8>) # running main() # registry -> [<function f1 at 0x016F6540>, <function f2 at 0x016F64F8>] # running f1() # running f2() # running f3() # 7.3 使用装饰器改进“策略”模式 # 使用注册装饰器可以改进 6.1 节中的电商促销折扣示例。 # 示例 7-3 promos 列表中的值使用 promotion 装饰器填充 # 7.4 变量作用域规则 # 在示例 7-4 中,我们定义并测试了一个函数,它读取两个变量的值:一个是局部变量 a,是函数的参数;另一个是变量 b,这个函数没有定义它。 # 示例 7-4 一个函数,读取一个局部变量和一个全局变量 # 示例 7-5 b 是局部变量,因为在函数的定义体中给它赋值了 # 示例 7-6 反汇编示例 7-4 中的 f1 函数 # 示例 7-7 反汇编示例 7-5 中的 f2 函数 # 7.5 闭包 # 在博客圈,人们有时会把闭包和匿名函数弄混。 # 其实,闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量。 # 这个概念难以掌握,最好通过示例理解。 # 示例 7-8 average_oo.py:计算移动平均值的类 # 示例 7-9 average.py:计算移动平均值的高阶函数 # 示例 7-10 测试示例 7-9 # 示例 7-11 审查 make_averager(见示例 7-9)创建的函数 # 示例 7-12 接续示例 7-11 # 7.6 nonlocal声明 # 示例 7-13 计算移动平均值的高阶函数,不保存所有历史值,但有缺陷 # 示例 7-14 计算移动平均值,不保存所有历史(使用 nonlocal 修正) # 7.7 实现一个简单的装饰器 # 示例 7-15 一个简单的装饰器,输出函数的运行时间 # 示例 7-16 使用 clock 装饰器 # 示例 7-17 改进后的 clock 装饰器 # 7.8 标准库中的装饰器 # Python 内置了三个用于装饰方法的函数:property、classmethod 和staticmethod。 # 另一个常见的装饰器是 functools.wraps,它的作用是协助构建行为良好的装饰器。 # 7.8.1 使用functools.lru_cache做备忘 # unctools.lru_cache 是非常实用的装饰器,它实现了备忘(memoization)功能。 # 这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。 # LRU 三个字母是“Least Recently Used”的缩写,表明缓存不会无限制增长,一段时间不用的缓存条目会被扔掉。 # 示例 7-18 生成第 n 个斐波纳契数,递归方式非常耗时 # 示例 7-19 使用缓存实现,速度更快 # 7.8.2 单分派泛函数 # 示例 7-20 生成 HTML 的 htmlize 函数,调整了几种对象的输出 # 示例 7-21 singledispatch 创建一个自定义的htmlize.register 装饰器,把多个函数绑在一起组成一个泛函数 # 7.9 叠放装饰器 # 7.10 参数化装饰器 # 示例 7-22 示例 7-2 中 registration.py 模块的删减版,这里再次给出是为了便于讲解 # 7.10.1 一个参数化的注册装饰器 # 示例 7-23 为了接受参数,新的 register 装饰器必须作为函数调用 # 示例 7-24 使用示例 7-23 中的 registration_param 模块 # 7.10.2 参数化clock装饰器 # 示例 7-25 clockdeco_param.py 模块:参数化 clock 装饰器 # 示例 7-26 clockdeco_param_demo1.py # 示例 7-27 clockdeco_param_demo2.py # 7.11 本章小结 # 本章介绍了很多基础知识,虽然学习之路崎岖不平,我还是尽可能让路途平坦顺畅。毕竟,我们已经进入元编程领域了。 # 开始,我们先编写了一个没有内部函数的 @register 装饰器;最后,我们实现了有两层嵌套函数的参数化装饰器 @clock()。 # 7.12 延伸阅读