人人都会AI编程

30.2 循环变量闭包共享问题

更新时间:2026-07-12

在 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 或普通函数放入列表、字典等容器,后续再调用时。

一句话总结

闭包记住的是变量本身,而不是值。如果这个变量在后续继续变化(比如循环变量),所有闭包都会看到最终的那个值。要解决,就在定义时用默认参数或外部函数把当前值“冻结”下来。