CodeWalk

Python陷阱大全:闭包延迟绑定与循环变量捕获

作者:屠龙少年 · 2026-05-30 12:55

请深入分析Python闭包陷阱的多种表现形式:for循环中的lambda、列表推导式中的lambda、threading回调中的延迟绑定。给出每种场景的解决方案并解释其字节码层面的原因。

回答

屠龙少年

经典场景

def create_functions():
    funcs = []
    for i in range(4):
        funcs.append(lambda: i)
    return funcs

for f in create_functions():
    print(f(), end=' ')  # 3 3 3 3

字节码层面: lambda内部有LOAD_DEREF指令加载自由变量ii存储在单元对象(cell object)中,闭包捕获的是cell的引用,而不是值的拷贝。循环结束后cell中的值为3。

列表推导式中的lambda

funcs = [lambda: i * i for i in range(4)]
for f in funcs:
    print(f(), end=' ')  # 9 9 9 9

Python 3.x中列表推导式有自己的作用域,但变量i仍然是在推导式的局部作用域中,所有lambda共享同一个i

解决方案总结

方案1:默认参数绑定(最常用)

for i in range(4):
    funcs.append(lambda i=i: i)  # 默认参数在定义时求值

方案2:functools.partial

from functools import partial
for i in range(4):
    funcs.append(partial(lambda x: x, i))

方案3:闭包工厂

def make_func(x):
    return lambda: x

funcs = [make_func(i) for i in range(4)]
# 输出:0 1 2 3

每个make_func调用创建新的作用域,x在每个作用域中独立。

方案4:生成器替代

def generate():
    for i in range(4):
        yield lambda: i

for f in generate():
    print(f(), end=' ')  # 0 1 2 3

生成器使用yield而不是return,保留了每次迭代时的状态。

核心原则: 不要让闭包共享可变循环变量,使用默认参数或工厂函数创建独立作用域。