2.闭包初探
为了说明闭包中引用的变量的性质,可以看一下下面的这个例子:
复制代码
1 def outer_func():
2 loc_list = []
3 def inner_func(name):
4 loc_list.append(len(loc_list) + 1)
5 print '%s loc_list = %s' %(name, loc_list)
6 return inner_func
7
8 clo_func_0 = outer_func()
9 clo_func_0('clo_func_0')
10 clo_func_0('clo_func_0')
11 clo_func_0('clo_func_0')
12 clo_func_1 = outer_func()
13 clo_func_1('clo_func_1')
14 clo_func_0('clo_func_0')
//代码效果参考:https://v.youku.com/v_show/id_XNjQwMDM2NjczNg==.html
15 clo_func_1('clo_func_1')
复制代码
程序的运行结果:
复制代码
clo_func_0 loc_list = [1]
clo_func_0 loc_list = [1, 2]
clo_func_0 loc_list = [1, 2, 3]
clo_func_1 loc_list = [1]
clo_func_0 loc_list = [1, 2, 3, 4]
clo_func_1 loc_list = [1, 2]
复制代码
从上面这个简单的例子应该对闭包有一个直观的理解了。运行的结果也说明了闭包函数中引用的父函数中local variable既不具有C++中的全局变量的性质也没有static变量的行为。
在python中我们称上面的这个loc_list为闭包函数inner_func的一个自由变量(free variable)。
If a name is bound in a block, it is a local variable of that block. If a name is bound at the module level, it is a global variable. (The variables of the module code block are local and global.) If a variable is used in a code block but not defined there, it is a free variable.
在这个例子中我们至少可以对闭包中引用的自由变量有如下的认识:
闭包中的引用的自由变量只和具体的闭包有关联,闭包的每个实例引用的自由变量互不干扰。
一个闭包实例对其自由变量的修改会被传递到下一次该闭包实例的调用。
由于这个概念理解起来并不是那么的直观,因此使用的时候很容易掉进陷阱。
3.闭包陷阱
下面先来看一个例子:
复制代码
1 def my_func(args):
2 fs = []
3 for i in xrange(3):
4 def func():
5 return i i
6 fs.append(func)
7 return fs
8
9 fs1, fs2, fs3 = my_func()
10 print fs1()
11 print fs2()
12 print fs3()
复制代码
上面这段代码可谓是典型的错误使用闭包的例子。程序的结果并不是我们想象的结果0,1,4。实际结果全部是4。
这个例子中,my_func返回的并不是一个闭包函数,而是一个包含三个闭包函数的一个list。这个例子中比较特殊的地方就是返回的所有闭包函数均引用父函数中定义的同一个自由变量。
但这里的问题是为什么for循环中的变量变化会影响到所有的闭包函数?尤其是我们上面刚刚介绍的例子中明明说明了同一闭包的不同实例中引用的自由变量互相没有影响的。而且这个观点也绝对的正确。
那么问题到底出在哪里?应该怎样正确的分析这个错误的根源。
其实问题的关键就在于在返回闭包列表fs之前for循环的变量的值已经发生改变了,而且这个改变会影响到所有引用它的内部定义的函数。因为在函数my_func返回前其内部定义的函数并不是闭包函数,只是一个内部定义的函数。
当然这个内部函数引用的父函数中定义的变量也不是自由变量,而只是当前block中的一个local variable。
复制代码
1 def my_func(*args):
//代码效果参考:https://v.youku.com/v_show/id_XNjQwMDM2ODI4MA==.html
2 fs = []
3 j = 0
4 for i in xrange(3):
5 def func():
6 return j * j
7 fs.append(func)
8 j = 2
9 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
复制代码