循环生成异步函数时为何错误绑定外层作用域变量?如何修复?
问题原因
这是Python闭包的迟绑定特性导致的:闭包内部引用的外层变量,并不是在函数定义时就固定下来,而是等到函数被调用时,才会去外层作用域查找该变量的当前值。
你在for循环里生成async函数时,每个coro都引用了同一个param变量。循环结束后,param的值已经变成了最后一个元素9,所以当你后续调用所有coro()时,它们都会取到9这个值,输出自然都是81。
解决方法
这里有几种靠谱的解决方式:
1. 利用函数默认参数绑定值
在定义coro时,把param作为默认参数传入,默认参数会在函数定义时就计算并绑定当前值:
import asyncio coroutines = [] for param in (1, 3, 5, 7, 9): # 用默认参数绑定当前循环的param值 async def coro(current_param=param): print(current_param ** 2) coroutines.append(coro) # 正确的异步运行方式:一次性运行所有协程 async def main(): await asyncio.gather(*[coro() for coro in coroutines]) asyncio.run(main())
2. 使用工厂函数生成协程
写一个专门的工厂函数,每次调用时传入当前的param,返回绑定了该值的协程函数:
import asyncio def create_coro(param): async def coro(): print(param ** 2) return coro coroutines = [] for param in (1, 3, 5, 7, 9): coroutines.append(create_coro(param)) async def main(): await asyncio.gather(*[coro() for coro in coroutines]) asyncio.run(main())
3. 使用functools.partial绑定参数
借助functools.partial把当前的param绑定到协程函数上:
import asyncio from functools import partial async def coro(param): print(param ** 2) coroutines = [] for param in (1, 3, 5, 7, 9): # 绑定当前param到coro的参数上 bound_coro = partial(coro, param) coroutines.append(bound_coro) async def main(): await asyncio.gather(*[coro() for coro in coroutines]) asyncio.run(main())
另外注意:你原来的代码里用for coro in coroutines: asyncio.run(coro())是不合适的,因为每次asyncio.run都会创建一个新的事件循环,正确的做法是用asyncio.gather在一个事件循环里运行所有协程,上面的示例已经修正了这个问题。
内容的提问来源于stack exchange,提问作者UpTheIrons
相关产品推荐
相关产品推荐

