生活在魔都的小明,终于攒够了首付,在魔都郊区买了一套房子;有一天,小明踩了狗屎,中了一注彩票,得到了20w,小明很是欢喜,于是想干脆用这20万来装修房子吧(decoration);
整个装修过程,小明费心费力,终于,装修结束了,小明入住了新家;
可是,住了一段时间,小明发现,白色的墙壁太没有逼格,怎么办呢?他想要重新刷墙(重构原始函数),但是作为程序猿的小明想到,以后总有一天新的墙面要看腻,为什么不在原来的墙面上贴上壁纸呢,选择还比较多;于是他采用了贴壁纸的方案(装饰器);
从此,小明过上了随意更改壁纸的快乐生活;
说到装饰器,最好先搞明白闭包,说到闭包的原理,就不得不提到命名空间,关于命名空间,这里不详赘述,可以看我的另一篇关于命名空间的博客;
python命名空间的查找顺序遵循LEGB规则,即:
局部(local)->封闭(closure)->全局(global)->内置(builtin);
E-Enclosing function locals;外部嵌套函数的名字空间(例如closure),作用范围为闭包函数;
G-Global(module);函数定义所在模块(文件)的名字空间,作用范围为当前模块;
B-Builtin(Python);Python内置模块的名字空间,作用范围为所有模块;
1.闭包
1.1 定义
首先让我们看一下维基上关于闭包的定义:
在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
<Python核心编程>里讲到:
如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。定义在外部函数内的但由内部函数引用或者使用的变量称为自由变量。
闭包将内部函数自己的代码和作用域以及外部函数的作用域结合起来。闭包的语法变量不属于全局名称空间或者局部的---而属于其他的名称空间,带着“流浪”的作用域(注意这不同于对象,因为那些变量存货在一个对象的名称空间;但是闭包变量存活在一个函数的名称空间和作用域)。
看了上面的文字是不是有些头昏,等你理解了闭包,回头来看,你就会觉得总结的很好;
看不懂,没关系,让我们通过一个例子来个初体验:
1.2 闭包小例子
1 def out_func(out_arg): 2 def in_func(in_arg): 3 return out_arg+in_arg 4 return in_func 5 6 add_10_counter = out_func(10) 7 print(add_10_counter(5)) 8 print(add_10_counter(8))
结果为:
15 18
让我们来分析一下这个函数:
1)功能很简单,创建了一个带有初始值10的加法器,通过提供一个数字,得到与10相加的结果;
2)功能是由一个out_func函数包裹了一个in_func函数实现的;当执行了add_10_counter = out_func(10)后,out_func(10)返回了一个函数对象in_func给add_10_counter,那么下次调用add_10_counter(x)时,实际上就是在调用in_func(x);但是我们看到in_func使用了变量out_arg,但是问题来了:out_arg即不在局部命名空间,也不在全局命名空间;为什么in_func可以使用out_arg呢?
根据闭包的定义,“如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。定义在外部函数内的但由内部函数引用或者使用的变量称为自由变量。”这样看,in_func就是一个闭包函数,而out_arg就是那个自由变量;而且我们注意到外部函数out_func调用已经结束了,相应的内存空间也已经销毁了,但是自由变量out_arg却没有销毁回收,生命周期得到了延长,为什么呢?这里就涉及到函数的__closure__属性了;
1.3 __closure__属性
在Python中,函数对象有一个__closure__属性,自由变量就被保存在函数对象的__closure__属性中
我们来验证这一点:
1 def out_func(out_arg): 2 def in_func(in_arg): 3 return out_arg+in_arg 4 return in_func 5 6 add_10_counter = out_func(10) 7 print(add_10_counter.__closure__) 8 print(add_10_counter.__closure__[0].cell_contents)