问题代码
def create_functions():
functions = []
for i in range(3):
# 创建一个函数,期望捕获当前循环的i值
functions.append(lambda: print(f"My value is: {i}"))
return functions
# 创建三个函数
f0, f1, f2 = create_functions()
# 调用这些函数
f0() # 期望输出 "My value is: 0"
f1() # 期望输出 "My value is: 1"
f2() # 期望输出 "My value is: 2"
但是实际输出为
My value is: 2
My value is: 2
My value is: 2
类似的,也可以不是用 lambda 表达式,而是使用函数实现闭包
# 依旧有问题
def create_functions():
functions = []
for i in range(3):
def func():
print(f"My value is: {i}")
functions.append(func)
return functions
问题原因解释
产生这样问题的原因是:python 闭包捕获了同一个外部变量 i,并且是通过变量名 i
而非 i
的地址作为索引(这一点很关键,虽然实际要比这个复杂,但是可以理解为就是通过名称确定某个变量的!)
- 如果不是通过变量
i
的字符串名字进行索引,也不会出现这个问题,实际上在for i in range(3)
过程中给你,i
的地址是一直变的 - 所以在最后
f0
、f1
、f2
都用过名字i
来找内存,找到了最后的那个 2 对应的内存地址!
两种解决方案
方案 1:把值通过变量传进去,此时闭包引用的是 func 的局部变量 x,而每一个函数实际都是不同的
def create_functions():
functions = []
for i in range(3):
def func(x):
return lambda: print(f"My value is: {x}")
functions.append(func(i))
return functions
# 创建三个函数
f0, f1, f2 = create_functions()
# 调用这些函数
f0() # 期望输出 "My value is: 0"
f1() # 期望输出 "My value is: 1"
f2() # 期望输出 "My value is: 2"
方案 2:使用函数入参默认值,因为 python 在定义函数默认值时,需要计算出来(这也是另外一个经常出 bug 的问题)
def create_functions():
functions = []
for i in range(3):
def func(x=i):
print(f"My value is: {x}")
functions.append(func)
return functions
# 创建三个函数
f0, f1, f2 = create_functions()
# 调用这些函数
f0() # 输出 "My value is: 0"
f1() # 输出 "My value is: 1"
f2() # 输出 "My value is: 2"
总结
在产生闭包(尤其是 lambda 表达式这种比较隐蔽时)时,一定要注意闭包中对外部变量的引用是否在发生改变,要仔细思考这些改变是否符合预期
不过,只要知道原理,相信可以很好的处理这些情况