Python中修改lambda调用处循环变量名为何导致执行结果变化?
问题复现
定义lambda函数的初始代码:
a = [] for i in range(3): a.append(lambda a:i+a)
第一种调用方式,循环变量使用j:
for j in range(3): print(a[j](0))
输出为三个2:
2 2 2
第二种调用方式,循环变量改用i,执行逻辑和前者完全一致:
for i in range(3): print(a[i](0))
输出为0、1、2:
0 1 2
根本原因
这个现象是两个Python语言特性共同作用的结果:
- 闭包延迟绑定:lambda(包括普通def定义的函数)捕获外部变量时,存储的是变量的引用,不是定义瞬间的值。函数真正执行的时候,才会去当前作用域查找对应变量名的最新值。
第一个调用场景里,定义完3个lambda后,外层作用域的循环变量i已经走完range(3)迭代,最终值固定为2。调用时的循环变量用的是j,不会修改外层的i,因此三个lambda执行时取到的i都是2,传入参数0时计算结果都是2+0=2,最终输出三个2。 - for循环变量无块级作用域:Python不存在很多其他语言常见的块级作用域,for循环声明的循环变量会泄露到外层作用域,循环结束后变量依然存在,值为最后一轮的迭代结果。
第二个调用场景里,循环变量名改成了i,这个i和之前定义lambda时外层作用域的i是同一个变量。循环执行过程中:- 第一轮迭代
i=0,调用第一个lambda,此时查找到的i值为0,计算得0+0=0 - 第二轮迭代
i=1,调用第二个lambda,此时i已经被循环修改为1,计算得1+0=1 - 第三轮迭代
i=2,调用第三个lambda,此时i值为2,计算得2+0=2
最终输出就是0、1、2。
- 第一轮迭代
修复方案
如果希望lambda在定义时就固定当时的i值,不受后续外部变量变动影响,可以把i作为lambda的默认参数传入——Python函数的默认参数是在函数定义时刻就完成求值绑定的:
a = [] for i in range(3): # 把i作为默认参数传入,定义时就固定对应值 a.append(lambda a, i=i: i+a)
用这种写法后,无论调用时的循环变量叫什么名字,输出都会是预期的0、1、2。
内容的提问来源于stack exchange,提问作者zo Aoo
相关产品推荐
相关产品推荐

