Python如何通过作用域变量实现闭包?解决for循环闭包变量捕获问题
问题描述
构造函数列表做并行执行时,编写验证脚本如下:
from multiprocessing import Process def runInParallel(*fns): processes = [] for fn in fns: p = Process(target=fn) p.start() processes.append(p) for p in processes: p.join() fns = [] def hi(x): print(x) # 实际运行时所有输出均为5 for i in range(6): fns.append(lambda: hi(i)) runInParallel(*fns)
预期每个lambda在定义时捕获当前轮次的i值,执行后输出0到5,实际所有函数运行时打印的i都是5。
问题根因
这个问题和多进程并行逻辑无关,是Python闭包的晚绑定特性导致的:
- Python闭包捕获的是外部变量的引用,不是函数定义那一刻变量的瞬时值
- for循环中的
i属于外层同一作用域的同一个变量,循环过程中只是它的值在不断更新,没有生成新的变量 - 等到列表里的lambda实际被调度执行时,for循环早就跑完了,此时
i的最终值是5,所有lambda读取到的i值自然全是5。哪怕不用多进程,顺序执行列表里的所有函数,也会得到一样的错误结果。
修复方案
任选以下一种方式即可实现循环变量的即时捕获,和现有多进程逻辑完全兼容:
- 利用函数默认参数的定义时绑定特性
Python函数的默认参数会在函数定义阶段完成求值绑定,不会等到函数执行时才去外层作用域查找变量,把循环变量作为默认参数传入lambda即可固定当前值:for i in range(6): fns.append(lambda cur_i=i: hi(cur_i)) - 使用
functools.partial提前绑定参数
标准库的partial可以提前给目标函数绑定固定入参,返回的新对象被调用时会直接传入预绑定的参数,不需要手写lambda逻辑:from functools import partial for i in range(6): fns.append(partial(hi, i)) - 通过工厂函数构造独立作用域
编写外层函数接收当前轮次的循环值,每次循环调用该工厂函数生成闭包,独立作用域内存储的变量不会随外层循环的i值变化:def hi_wrapper(current_i): def run(): return hi(current_i) return run for i in range(6): fns.append(hi_wrapper(i))
修改完成后再执行脚本,即可得到0到5的预期输出。
内容的提问来源于stack exchange,提问作者Zach Smith
相关产品推荐
相关产品推荐

