在 Python 中,如果你在循环里定义了一个函数(或 lambda),并且这个函数引用了循环变量,你很可能会掉进一个经典的坑:所有函数最终共享了同一个循环变量,而不是分别记住当时的值。
问题复现
# 你想创建 3 个分别打印 0、1、2 的函数
funcs = []
for i in range(3):
funcs.append(lambda: print(i))
# 依次调用它们
for f in funcs:
f()
你的预期输出可能是 0 1 2,但实际输出是:
2
2
2
为什么会这样?
- Python 里的循环变量
i并不随着每次迭代“独立”。i是一个存在于封闭作用域( enclosing scope)中的变量,它的值在循环过程中不断改变。 - lambda 里面的
print(i)并没有在定义时捕获i的当前值,而是记住了一个对变量i的引用。当 lambda 被真正调用时,它去查找i,此时循环早已结束,i指向最终值2。 - 所有 lambda 引用的都是同一个
i,所以全部输出2。
解决方案
方法一:使用默认参数“冻结”当前值(最常见)
利用函数默认参数只在定义时求值一次的特性,把循环变量的当前值作为默认参数传进去:
funcs = []
for i in range(3):
funcs.append(lambda x=i: print(x)) # x=i 在定义时就已经绑定当前值
for f in funcs:
f()
# 输出:0 1 2
每个 lambda 有了独立的默认参数 x,其值在循环的每一步被“冻结”,不再随 i 变化。
方法二:用另一个函数封闭作用域
将循环体包装成一个函数,让变量通过函数参数传入,形成独立的作用域:
def make_printer(n):
return lambda: print(n)
funcs = []
for i in range(3):
funcs.append(make_printer(i))
for f in funcs:
f()
# 输出:0 1 2
这里每次调用 make_printer(i) 都会创建一个新的作用域,n 在那个作用域中是固定不变的,lambda 引用的是各自的 n。
方法三:利用 functools.partial
from functools import partial
def my_print(n):
print(n)
funcs = []
for i in range(3):
funcs.append(partial(my_print, i))
for f in funcs:
f()
# 输出:0 1 2
partial 在构造时就把 i 的值固定了,效果类似默认参数。
什么时候会遇到这个问题
- 在为按钮、回调等 UI 组件批量添加事件处理函数时。
- 在创建一组定时任务或钩子函数时。
- 在把 lambda 或普通函数放入列表、字典等容器,后续再调用时。
一句话总结
闭包记住的是变量本身,而不是值。如果这个变量在后续继续变化(比如循环变量),所有闭包都会看到最终的那个值。要解决,就在定义时用默认参数或外部函数把当前值“冻结”下来。