Python基础(五)——闭包与lambda的结合
(1)变量的域
要了解闭包需要先了解变量的域,也就是变量在哪一段“上下文”是有效的(类似局部变量和全局变量的区别),举一个很简单的例子。(例子不重要,就是涉及闭包就要时刻关注这个域)
1 def test(): 2 msg2 = 'test中的' 3 print('====',msg1) # ==== 非test中的 4 msg1 = '非test中的' 5 test() 6 print(msg1) # 非test中的 7 print(msg2) # 报错
(2)什么是闭包
维基百科定义:闭包(Closure)或闭包函数(function Closure),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
定义可以反复体会,先看一个比较有意思的例子:
1 def test(): 2 msg = '我是test中的' 3 def test2(): 4 print(msg) 5 return test2 6 g = test() 7 g() # 我是test中的
这段代码执行到第七行的时候,输出了msg的内容。
我们在对比【(1)变量的域】中的代码例子,那个例子中,同样是第七行的时候是报错,因为很好理解,上一个例子msg2已经脱离了test()函数,也就是局部变量只能在内部使用,不能够全局使用。
然后回到这个例子,这就是闭包存在的意义,我们可以在外部访问局部变量。闭包就是:函数+上下文。注意到我这个例子第五行返回的是test2,是一个函数对象(in Python everything is an object )。这里的 g 就是闭包。而我所说的上下文:就是各种变量环境。所以第七行的return,返回的function不是普通function,是带着上下文环境一起的(也就是test2() 函数中带有msg,而msg其实实在test()中定义的,但是也会被test2()带在身边)。不单单只返回第三第四行两行简单的内容。
(3)闭包疑点
我们再看几个 stockoverflow 和 官网上的几个关于闭包的例子与疑点:
1.例子一
1 adders=[0,1,2,3] 2 3 for i in [0,1,2,3]: 4 adders[i]=lambda a: i+a 5 6 print(adders[1](3)) # 6
这个里之中adders列表存储了匿名函数,adders[1](3) 就是访问adders[1] 中的匿名函数,参数是3,也就是lambda a:i+a(3传递给a,i 是for...in 循环给的,计算结果是 i + a)。
奇怪的是结果adders[1](3) = 6.我们可能会想应该是4阿,1 + 3 = 4。
我的理解是这样的:因为我们需要注意这里的 i 到底是属于谁的,i 是在 for...in 中定义的,一个循环至始至终只有一个 i ,也就是 i 的引用是不变的,变得是 i 得值,所以lambda中的 i ,adders[0],adders[1]....中的 i 都是指向同一个 i ,而最后 i 是三。adders[0,1,2,3] 中的 lambda 匿名函数的参数 i 全都是同一个,这个 i 因为循环最终值是3. 所以3+3=6.(也就是 i 是什么时候定义的?这个问题考虑好,是在调用lambda之前,也就是for循环开始的时候定义好的)
改进方案:
1 adders=[0,1,2,3] 2 3 for i in [0,1,2,3]: 4 adders[i]=lambda a, b = i: b+a 5 6 print(adders[0](3)) # 3 7 print(adders[1](3)) # 4 8 print(adders[2](3)) # 5 9 print(adders[3](3)) # 6
那我们就在lambda之中定义一个b,这个b是记录i,但是adders[ ....] 数组中的 b 是各不相同的引用哦。
2.例子二
1 squares = [] 2 for x in range(5): 3 squares.append(lambda: x**2) 4 5 print(squares[2]()) # 16 6 print(squares[3]()) # 16
情况一模一样。最后全都算 4 *4 = 16。改进如下:
1 squares = [] 2 for x in range(5): 3 squares.append(lambda b = x: b**2) 4 5 print(squares[2]()) # 4 6 print(squares[3]()) # 9