这章讲的是函数装饰器和闭包。函数装饰器在Python框架、类库中大量使用,不懂这个是看不懂也无法理解它们的。闭包其实是存在了很久的概念,只是在函数式编程逐渐兴起之后,开始被广大程序猿群众熟知。特别是在JavaScript里面,到处都是闭包。

作为补充,可以阅读以下材料:Python学习手册(第4版)-第8部分&附录。这份材料是出版社自行公开的。因为如果印制出来,书籍太厚了,所以高级部分没有印刷,放出了电子版。下载学习不侵权。

链接: https://pan.baidu.com/s/1-45ok9ENZ9vHi65A82Aduw 密码: ewg5

文档里面有一章 – 第38章,专门讲了Python装饰器。

严格意义上,装饰器是一种语法糖,你总可以定义一个函数,把另一个函数作为参数传进去,然后操作修改后返回来。但是装饰器是很方便的函数行为修改、操纵、管理手段,开发框架用的特别多,比如路由定义、登录检测等等。在印象中PHP/JavaScript/Java这些语言没有类似的设计。

延续上一章,如果使用装饰器来定义所谓的“策略模式”,是很简单的。但是大多数装饰器都会修改函数本身,这就牵扯到了闭包话题。

讨论闭包之前,先要研究下Python的变量作用域问题。Python为了避免无意修改全局变量,在函数中修改全局变量的值,需要提前使用global声明。这点可比JavaScript好多了。

闭包概念容易和匿名函数搞混,因为它们经常在一起混。抓住要点:闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没关系,关键是它能访问定义体之外的非全局变量。

因为Python保存了变量的绑定信息,所以即便定义作用域消失了,它仍然能够访问那些变量。其它语言,可以一样去理解,差别只是语法和实现而已。Python为定义体外的非全局变量赋值,需要使用nonlocal声明。

Python标准库中的functools提供了一些常用装饰器,比如functors.wraps/lru_cache/singledispatch

lru_cache有个重要特性:缓存结果。还记得算法里面经常讨论的对一些递归计算问题的优化么?比如斐波那契数列计算,实际上大量的计算是重复的,如果能够缓存前面的计算结果,就可以大大的加快运算速度。使用lru_cache也特别的简单,直接给要缓存的函数装饰下即可。但是要注意:修饰的函数必须是没有副作用的,而且最好不要 返回可变(mutable)对象。

实现代码:https://github.com/python/cpython/blob/3.4/Lib/functools.py

lru_cache()调用的主要操作就是包裹被调用的函数,然后把函数参数信息当成key,保存计算结果信息,后面查询计算需求,直接返回保存结果。还是很容易懂的。它有两个要注意的地方,它可以传递参数进去,设置缓存的数量,但是建议maxsize应该是2的幂;被调用的函数,参数必须都是可散列的 – 这意味着数据不变性,列表是不行的。

后面讨论singledispatch装饰器。它解决的是Python函数没法简单的依靠变量类型重载的问题 – 动态语言。比如,你要给传入的数据,分门别类的进行一些定制化的操作,常规办法只能是if/else/elif/else这些判断,这带来代码维护的困难。

singledispatch解决了这个问题。具体使用参考文档。

装饰器还有叠放/参数的问题。所谓叠放,就是一次用好几个装饰器修饰函数。这个容易理解,一层层嵌套呗。参数化也容易明白,根据参数对装饰器做一些定制化。比如前面提到的lru_cache,参数可以设置缓存大小。

 

流畅的Python-Fluent Python读书笔记-08-对象引用、可变性和垃圾回收
流畅的Python-Fluent Python读书笔记-06-使用一等函数实现设计模式