1. 闭包的概念
闭包(closure):
内部函数中对enclosing作用域中的变量进行引用,这个内部函数就称为闭包。
关于enclosing作用域相关的知识可以参考>>传送门 - Python函数作用域<<。
2. 举例解释
首先要理解python中的函数也是一个对象,它是有属性和返回值的,而在函数执行完后其内部变量会被解释器回收。
1 | # G |
1 | 运行结果: |
两个本来语句1运行完之后,func内部的变量都已经被回收了,那么这个 80为什么还可以被语句2打印出来呢?
注意下这个现象f.__closure__这个元组的第一个元素的id和score变量的id值相同。
说明如果内部函数中对enclosing作用域中的变量进行引用之后,这个变量就会被添加到inner_func的__closure__属性这个元组中,这样因为有了这个添加到元组的引用,score变量就不会被回收了,再调用f函数时,就会在它的__closure__属性中查找score变量,这样就自然而然的可以引用了。
理解了这个例子就可以搞懂Python的闭包了。
3. 一个简单的应用
下面的代码就是应用闭包达到了自定pass_line的目的
1 | def func(pass_line): |
1 | 运行结果: |
4. 经典错误代码
参考>>传送门 - 闭包经典错误代码<<
1 | def foo(): |
1 | 会报错如下: |
这是因为在执行代码 c = foo()时,python会导入全部的闭包函数体bar()来分析其的局部变量,
python规则指定所有在赋值语句左面的变量都是局部变量,
则在闭包bar()中,变量a在赋值符号”=”的左面,被python认为是bar()中的局部变量。
再接下来执行print c()时,程序运行至a = a + 1时,因为先前已经把a归为bar()中的局部变量,
所以python会在bar()中去找在赋值语句右面的a的值,结果找不到,就会报错。解决的方法很简单:
1 | def foo(): |
只要将a设定为一个容器就可以了。这样使用起来多少有点不爽,
所以在python3以后,在a = a + 1 之前,使用语句nonloacal a就可以了,该语句显式的指定a不是闭包的局部变量。