但这里的问题是为什么for循环中的变量变化会影响到所有的闭包函数?尤其是我们上面刚刚介绍的例子中明明说明了同一闭包的不同实例中引用的自由变量互相没有影响的。而且这个观点也绝对的正确。
那么问题到底出在哪里?应该怎样正确的分析这个错误的根源。
其实问题的关键就在于在返回闭包列表fs之前for循环的变量的值已经发生改变了,而且这个改变会影响到所有引用它的内部定义的函数。因为在函数my_func返回前其内部定义的函数并不是闭包函数,只是一个内部定义的函数。
当然这个内部函数引用的父函数中定义的变量也不是自由变量,而只是当前block中的一个local variable。
def my_func(*args):
fs = []
j = 0
for i in xrange(3):
def func():
return j * j
fs.append(func)
j = 2
return fs
上面的这段代码逻辑上与之前的例子是等价的。这里或许更好理解一点,因为在内部定义的函数func实际执行前,对局部变量j的任何改变均会影响到函数func的运行结果。
函数my_func一旦返回,那么内部定义的函数func便是一个闭包,其中引用的变量j成为一个只和具体闭包相关的自由变量。后面会分析,这个自由变量存放在Cell对象中。
使用lambda表达式重写这个例子:
1 def my_func(*args): 2 fs = [] 3 for i in xrange(3): 4 func = lambda : i * i 5 fs.append(func) 6 return fs
经过上面的分析,我们得出下面一个重要的经验:返回闭包中不要引用任何循环变量,或者后续会发生变化的变量。
这条规则本质上是在返回闭包前,闭包中引用的父函数中定义变量的值可能会发生不是我们期望的变化。
正确的写法:
def my_func(*args):
fs = []
for i in xrange(3):
def func(_i = i):
return _i * _i
fs.append(func)
return fs
或者:
1 def my_func(*args): 2 fs = [] 3 for i in xrange(3): 4 func = lambda _i = i : _i * _i 5 fs.append(func) 6 return fs
正确的做法便是将父函数的local variable赋值给函数的形参。函数定义时,对形参的不同赋值会保留在当前函数定义中,不会对其他函数有影响。
另外注意一点,如果返回的函数中没有引用父函数中定义的local variable,那么返回的函数不是闭包函数。
4.闭包的应用自由变元可以记录闭包函数被调用的信息,以及闭包函数的一些计算结果中间值。而且被自由变量记录的值,在下次调用闭包函数时依旧有效。
根据闭包函数中引用的自由变量的一些特性,闭包的应用场景还是比较广泛的。后面会有文章介绍其应用场景之一——单例模式,限于篇幅,此处以装饰器为例介绍一下闭包的应用。
如果我们想对一个函数或者类进行修改重定义,最简单的方法就是直接修改其定义。但是这种做法的缺点也是显而易见的:
可能看不到函数或者类的定义
会破坏原来的定义,导致原来对类的引用不兼容
如果多人想在原来的基础上定制自己函数,很容易冲突
使用闭包可以相对简单的解决上面的问题,下面看一个例子:
def func_dec(func):
def wrapper(*args):
if len(args) == 2:
func(*args)
else:
print 'Error! Arguments = %s'%list(args)
return wrapper
@func_dec
def add_sum(*args):
print sum(args)
# add_sum = func_dec(add_sum)
args = range(1,3)
add_sum(*args)
对于上面的这个例子,并没有破坏add_sum函数的定义,只不过是对其进行了一层简单的封装。如果看不到函数的定义,也可以对函数对象进行封装,达到相同的效果(即上面注释掉的13行),而且装饰器是可以叠加使用的。
4.1 潜在的问题