Python列表推导式、全局for、函数内for生成lambda列表的行为差异
问题背后的核心机制:Python闭包的迟绑定特性
所有lambda函数都是引用了外部变量i的闭包,Python闭包的变量取值遵循迟绑定规则:变量的值只会在闭包被调用时才查找,不会在闭包定义时就固定下来,这是你看到不同写法结果不同的根本原因。
另外首先需要纠正一个误解:你观察到plus_n_符合预期只是测试代码的巧合,和它定义在顶层作用域没有任何关系。你只要把测试代码的循环变量名改成j,就会发现plus_n_的所有输出也全部是7,和另外两种写法结果完全一致:
# 修改测试循环的变量名 for j in range(len(plus_n)): print('plus_n[{}]({})={}'.format(j,3,plus_n[j](3))) print('plus_n_[{}]({})={}'.format(j,3,plus_n_[j](3))) print('plus_n__[{}]({})={}'.format(j,3,plus_n__[j](3))) print()
三种写法的差异详解
1. 列表推导式生成的plus_n
Python 3中列表推导式拥有独立的作用域,循环结束后该作用域内的i最终值为range(MAX)的最后一个元素4。所有lambda被调用时,查找的都是这个已经固定为4的i,因此所有计算结果都是3+4=7。
2. 顶层循环生成的plus_n_
你原测试代码的循环变量正好也叫i,和lambda引用的全局i是同一个变量。每次调用lambda时,全局i正好是当前测试循环的取值,所以刚好匹配你的预期。如果修改测试循环变量名,所有lambda调用时取到的都是全局i最后一次赋值的4,结果全部为7。
3. 函数内生成返回的plus_n__
all_plus_n函数执行时,内部循环的i最终值为4。函数执行结束后,闭包会保留对函数作用域内这个i的引用,因此所有lambda调用时取到的都是4,结果全部为7。
正确实现方法
不需要将lambda列表定义为全局变量,只需要在定义lambda时用参数默认值提前绑定当前的i即可。参数默认值会在函数定义阶段就求值固定,不会等到调用时再查找:
MAX=5 # 列表推导式写法 plus_n=[lambda x, i=i: x+i for i in range(MAX)] # 顶层循环写法 plus_n_=[] for i in range(MAX): plus_n_.append(lambda x, i=i: x+i) # 函数返回写法 def all_plus_n(): plus_ns=[] for i in range(MAX): plus_ns.append(lambda x, i=i: x+i) return plus_ns plus_n__=all_plus_n()
修改后三种写法的运行结果完全一致,全部符合预期。
内容的提问来源于stack exchange,提问作者Jonathan alis
相关产品推荐
相关产品推荐

