Python闭包绑定变量引用而非值,导致循环中多个闭包共享同一变量最终值;正确做法是用默认参数固化当前值或通过封装函数传入,可通过__closure__和co_freevars验证闭包结构。

Python闭包中变量绑定发生在内层函数定义时,而非调用时;但绑定的是变量的引用,不是值本身——这导致常见“循环中闭包捕获相同变量”的问题。
闭包如何捕获外部变量
当内层函数(如嵌套函数)引用了外层函数的局部变量,且该内层函数在外部被返回或传递出去,就构成闭包。此时 Python 会把被引用的变量打包进内层函数的 __closure__ 属性中,每个元素是一个 cell 对象,保存对实际对象的引用。
- 闭包捕获的是“名字”对应的当前绑定,不是快照值
- 如果外层变量后续被修改,闭包里看到的就是新值(除非该变量是不可变对象且未被重赋值)
- 关键点:for 循环中定义多个闭包,它们共享同一个循环变量名的 cell 引用
典型陷阱:循环中创建闭包
如下代码本意是生成 3 个函数,分别返回 0、1、2,但实际都返回 2:
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs]) # [2, 2, 2]
原因:所有 lambda 都闭包了同一个变量 i,循环结束时 i == 2,所以每次调用都读取这个最终值。
立即学习“Python免费学习笔记(深入)”;
- ✅ 正确写法:用默认参数固化当前值:
lambda x=i: x - ✅ 或改用闭包函数封装:
def make_f(x): return lambda: x,再funcs.append(make_f(i)) - ⚠️ 注意:
nonlocal i在这种场景不适用,因为i是 for 的迭代变量,不是外层函数的局部变量
验证闭包结构:看 __closure__
可通过函数对象的 __closure__ 和 __code__.co_freevars 查看实际绑定的变量:
def outer(x):
def inner():
return x
return inner
f = outer(42)
print(f.code.co_freevars) # ('x',)
print(f.closure[0].cell_contents) # 42
-
co_freevars是元组,列出闭包引用的变量名 -
__closure__是元组,每个cell对应一个自由变量,其cell_contents是当前值 - 若函数无自由变量,
__closure__为None
可变对象 vs 不可变对象的影响
闭包绑定的是引用,因此对可变对象(如 list、dict)的原地修改,会影响所有闭包:
def make_adders():
data = []
res = []
for i in range(3):
res.append(lambda: data.copy()) # 每次都返回当前 data 副本
data.append(i)
return res
adders = make_adders()
print([f() for f in adders]) # [[0], [0, 1], [0, 1, 2]]
- 所有 lambda 共享同一份
data列表 - 每次调用
lambda时才执行data.copy(),所以得到不同状态 - 若改为
lambda d=data: d.copy(),则每个闭包固定绑定循环当时的data引用(仍是同一对象),结果不变










