闭包
在一个函数内部定义另一个函数,外部的函数为外函数,内部的函数为内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这就形成了一个闭包。
通常情况下,一个函数运行结束后,函数内部的所有东西都会被释放掉,局部变量也会消失。但是如果外函数在结束时发现自己的临时变量会在内函数中用到时,就会把这个临时变量绑定给内函数,然后外函数才结束。
有一道很经典的案例:
def multipliers():
return [lambda x: i * x for i in range(4)]
print ([m(2) for m in multipliers()])
可能我们会认为,上面的函数将输出为[0, 2, 4, 6],但其实输出的是[6, 6, 6, 6],这就是闭包导致的。
闭包函数返回内部函数的引用(即定义的lambda匿名函数),外函数调用结束时会将被内函数引用的局部变量(亦称为闭包变量)绑定至内函数。而延迟绑定指的是,只有在调用内函数时,才会访问闭包变量所指向的对象,不调用时不会访问闭包变量所指向的对象。
上面的例子相当于:
def multipliers():
funcs = []
for i in range(4):
def bar(x):
return x*i
funcs.append(bar)
return funcs
print ([m(2) for m in multipliers()] )
来看下不调用内函数的情况:
def multipliers():
result = []
for i in range(4):
print(i)
def func(x):
print("test")
return i * x
result.append(func)
return result
multipliers()
输出为
0
1
2
3
可以从上面的代码看出,未输出test字符,说明我们没有调用内函数,在我们没有调用内函数的同时,循环的 i 变量最后指向了3,当外函数发现自己的临时变量会在内函数中用到时,就把i=3绑定给了内函数,这时外函数已经调用结束。所以当我们调用内函数时,相当于这段伪代码(不能执行的代码):
return [lambda x: 3 * x, lambda x: 3 * x, lambda x: 3 * x, lambda x: 3 * x]
所以输出的结果为[6,6,6,6]
解决方法
将每个循环的 i 值绑定给内函数就行了,也叫做立即绑定
多加个默认参数 i=i
def multipliers():
return [lambda x,i=i: i * x for i in range(4)]
相当于:
def multipliers():
funcs = []
for i in range(4):
def bar(x, i=i):
return x * i
funcs.append(bar)
return funcs
print ([m(2) for m in multipliers()] )
这样子就把循环过程的每个 i 变量都绑定到内函数了,相当于这段伪代码:
return [lambda x: 0 * x, lambda x: 1 * x, lambda x: 2 * x, lambda x: 3 * x]
还有一种方法是,采用生成器的方法:函数调用,执行到yield时,就会挂起,等下一次调用时,才会执行yield下面的部分代码(这样就避免一次性执行完for循环):
def multipliers():
for i in range(4):
yield lambda x : i * x
print([m(2) for m in multipliers()])