闭包
如果在一个内部函数中,对外部作用域(但不是全局作用域)的变量进行引用,那么内部函数就被称为闭包,闭包是由函数及其相关的引用环境组合而成的实体(闭包=函数+引用环境),函数可以作为另一个函数的参数或返回值,可以赋值给一个变量,函数可以嵌套定义,即在一个函数内部再定义一个函数,有了嵌套函数这种结构,便产生了闭包。
闭包示例
1 | def counter(start_num): |
2 | num=start_num |
3 | def incr(): |
4 | return num+1 |
5 | return incr |
6 | 10) count=counter( |
7 | count() |
8 | 11 |
9 | 20) count1=counter( |
10 | count1() |
11 | 21 |
12 | count() |
13 | 11 |
这里内部函数incr的执行结果得到的相互隔离的,也就是说明在每次调用外部counter函数的时候会生成一个新的局部变量num,这里外部变量函数返回的就是闭包。
按照命令式语言的规则,外部函数只是返回了内嵌函数的地址,在执行内嵌函数时将会由于在其作用域内找不到外部变量而出错。而在函数式语言中,当内嵌函数体内引用到体外的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体(闭包)返回。由于闭包把函数和运行时的引用环境打包成一个新的整体,所以解决函数嵌套所引发的问题,如上述实例中,每次执行外部函数都返回一个新的闭包实例,这些实例是隔离的。
所以python中闭包的定义为:如果一个内部函数对外部函数作用域(不是全局作用域)的变量进行引用,那么内部函数就被认为是闭包。
闭包注意事项
- 闭包不能修改外部作用域的局部变量分析下原因:当执行代码
1
def counter(start_num):
2
num=start_num
3
def incr():
4
... num+=1 #修改外部局部变量
5
return num+1
6
return incr
7
10) count=counter(
8
count()
9
Traceback (most recent call last):
10
File "<stdin>", line 1, in <module>
11
File "<stdin>", line 4, in incr
12
UnboundLocalError: local variable 'num' referenced before assignment
count=counter(10)
的时候,外部的局部变量的闭包会被导入incr(),而python默认查找namespace的方式为local namespace->global namespace->build-in namespace来查找变量,而在闭包中“num=”等于在定义local namespace,所以python认为num是闭包中的局部变量,所以会出现异常num未定义。
python2中的解决方法,为num找一个容器。
1 | def counter(start_num): |
2 | num=[start_num] |
3 | def incr(): |
4 | 0]+=1 num[ |
5 | return num[0] |
6 | return incr |
7 |
|
8 | 10) count=counter( |
9 | count() |
10 | 11 |
11 | count() |
12 | 12 |
python3中更优雅的解决方法,显式指明num不是闭包的局部变量。
1 | def counter(start_num): |
2 | num=start_num |
3 | def incr(): |
4 | nonlocal num |
5 | 1 num+= |
6 | return num |
7 | return incr |
8 |
|
9 | 10) count=counter( |
10 | count() |
11 | 11 |
12 | count() |
13 | 12 |
- 闭包的陷阱
陷阱代码:
1 | def count(): |
2 | fs=[] |
3 | for i in range(1,4): |
4 | def f(): |
5 | return i*i |
6 | fs.append(f) |
7 | return fs |
8 |
|
9 | f=count() |
10 | f |
11 | [<function f at 0x109df7f50>, <function f at 0x109e02050>, <function f at 0x109e020c8>] |
12 | print(f1(),f2(),f3()) |
13 | (9, 9, 9) |
我们都知道函数只会在调用的时候执行,而从上述的函数调用的过程中,首先执行了count(),而内部的函数还未执行,所以在整个执行过程中i的变量已经被循环成3了,所以当执行内部函数闭包时候传入的变量i应该都是3,所以执行为9是正确的。
我们验证下:
1 | def count(): |
2 | fs=[] |
3 | for i in range(1,4): |
4 | def f(): |
5 | return i*i |
6 | fs.append(f) |
7 | print(locals()) |
8 | return fs |
9 | c=count() |
10 | {'f': <function count.<locals>.f at 0x1053532f0>, 'fs': [<function count.<locals>.f at 0x105353510>, <function count.<locals>.f at 0x105353400>, <function count.<locals>.f at 0x1053532f0>], 'i': 3} |
i确实是3,所以猜想是正确的。
解决方法:
1 | def count(): |
2 | fs=[] |
3 | for i in range(1,4): |
4 | def f(y=i): |
5 | return y*y |
6 | fs.append(f) |
7 | return fs |
8 |
|
9 | f=count() |
10 | for i in f: print(i()) |
11 | 1 |
12 | 4 |
13 | 9 |
这里的结局思路就是,当闭包需要引入外部变量的时候,直接将外部的变量赋值到内部函数的局部变量即可以解决这种问题。
总结:
- 一个函数中,内部函数需要调用外部(非全局变量)的变量,我们就称这个内部函数是闭包
- 每次外部调用外部函数变量会形成新的闭包,两个闭包环境相互隔离
- 闭包不能修改外部作用域的局部变量
- 闭包想要修改外部的局部变量,python2需要找到一个容器,python3中可以显式告诉函数非闭包局部变量
- python函数只有在调用的时候才会执行