什么是闭包
闭包是指可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。— 百度百科
其实意思也很好懂了,我们知道程序都有域的概念,从顶级的命名空间到java/C++等{ }的域界定符,程序就是代码块或嵌套或并列的堆砌。域的作用是限制了变量的作用域:每个变量都有自己的作用域(Python同级和子级的缩进,java的{}内),不在作用域内是无法使用变量的。然而闭包的存在一定意义上打破了这种限制,使得程序看起来像是变量被作用域越界访问了。
之所以是看起来像,是因为的确是看起来像,Python中闭包一般是通过某个函数返回一个内嵌函数来实现的。这个内嵌函数引用了外部函数的一个变量,当内嵌函数被外部函数返回的时候,如果在外部执行内嵌函数对象的时候,Python通过"LEGB法则" 使得内嵌代码块和其运行环境(引用的外部函数的变量,但此时和执行包装对象函数已经是平行的、相互独立的域了,理论上不可以访问)仿佛捆绑为一起返回一个包装的对象(Python中一切都是对象)。也就是说闭包封存函数执行的上下文环境。下面会有简单的代码形象的解释该流程。
闭包有什么用
闭包使得某个函数的内嵌函数,可以在这个函数的外部被直接调用。 不需要刻意的使用闭包,走心不走形。闭包主要是为了能够保证一个函数在被传递的时候,其可能依赖的局部变量(运行环境)可以随着函数体一起传递,保证了函数依然能够准确的运行!
闭包怎么写
这就是一个简单的闭包:
def outer():
out_ref = 'i am outer'
def inner():
print(out_ref)
return inner
f = outer()
f()
# 输出: i am outer
程序中我们可以看到:
- outer() 返回了inner这个引用,这个引用指向了inner函数这个对象。
- f() == inner() 即执行inner()函数体,输出结果为i am outer
问题就出在这里:inner引用了outer中的out_ref变量,out_ref的作用域是 outer函数体内部(下方的绿色块区域),所以在outer内使用out_ref是没有问题的。但是调用inner()的地方是f(), f() 和outer函数是平级,按道理来讲:f()是无法访问out_ref变量的,因为f()的代码块不在out_ref的作用域内。然而f() 却正确的使用了out_ref变量,这就是闭包特性的体现: 它封存了inner函数被返回时候的上下文! 也就是说outer()返回inner的时候,将out_ref和函数体对象仿佛捆绑在一起返回,使得我们依然可以在作用域范围外使用变量。
闭包实现机制
上面一直在说仿佛,因为Python并不是真的有这么个将函数体和运行环境上下文捆绑的对象,他是通过LEGB法则来实现的。
LEGB
LEGB法则指定了python遇到变量的时候如何去查询这个被引用变量的位置。 LEGB含义解释
- L-Local(function);函数内的名字空间(inner中)
- E-Enclosing function locals;外部嵌套函数的名字空间(outer中)
- G-Global(module);函数定义所在模块(文件)的名字空间
- B-Builtin(Python);Python内置模块的名字空间
查找按照L->E->G->B顺序进行,只要遇到了符合要求的变量就停止继续查找,如果一直到头都没有找到就抛出NameError异常。 这就是LEGB规则。
内嵌函数闭包的条件
- 存在嵌套函数(outer嵌套inner)
- 内部函数引用外部函数变量(inner引用out_ref)
- 在和外部函数平级的空间里面调用了内部函数()
那么外部函数的变量将和内部函数整体为一个闭包!
闭包会引发的错误 始终注意:f 是函数的引用,f在引用中是不会执行函数的,它会保存引用out_ref的状态,但是一旦out_ref变了,只要你没有调用f(),那么再次调用f()的结果也变为out_ref变化后的执行结果,所以闭包返回函数的时候不要引用一个会自变化的量。f() 才是函数的执行,它才会直接使用out_ref变量将结果运算出来。