为何Python中列表推导式创建4个协程仅触发1条RuntimeWarning,多次直接赋值却触发4条?
为何Python中列表推导式创建4个协程仅触发1条RuntimeWarning,多次直接赋值却触发4条?
这是个非常有意思的细节问题,本质和Python的垃圾回收时机、协程对象的警告触发逻辑,以及CPython警告系统的去重规则密切相关,咱们一步步拆解:
1. 先搞懂警告的触发逻辑
RuntimeWarning的触发时机是未被await的协程对象被垃圾回收时——协程对象的__del__方法会检查自身是否被正确await过,若没有则抛出该警告。
2. 两种创建方式的核心差异
情况1:列表推导式创建4个协程(仅触发1次警告)
coros = [some_async_func() for _ in range(4)]
这4个协程对象都被coros列表持有引用,所以在main函数执行过程中,它们不会被垃圾回收。只有当main函数执行完毕,coros变量离开作用域,整个列表被回收时,里面的4个协程才会被批量销毁。
而CPython的警告系统默认会对同一代码位置产生的相同警告进行去重——这4个协程都来自同一行代码(列表推导式的那一行),所以它们的警告被视为“同一来源”,最终只输出1次。
情况2:4次单独赋值协程(触发4次警告)
z = some_async_func() z = some_async_func() z = some_async_func() z = some_async_func()
每次给z赋值新的协程对象时,前一个z引用的旧协程就失去了所有引用,会被Python的垃圾回收器即时回收(小对象的快速回收机制)。更关键的是:每个协程都来自不同的代码行,所以它们的警告被视为“不同来源”,不会被去重,最终输出4次警告。
3. 同时打开两个例子为何只触发4次警告?
当你同时启用两段代码时:
- 4次单独赋值的协程中,前3个会在被覆盖时即时回收,触发3次警告;第4个协程会和
coros列表一起,在main函数结束后才被回收。 - 而当程序即将退出时,Python会进入收尾清理阶段,此时CPython可能会跳过部分对象的
__del__方法执行(或警告系统已停止工作),导致coros列表里的4个协程以及第4个单独赋值的协程中,只有1次警告被输出(或者都被抑制)。最终总警告数和单独启用第二个例子时一致,为4次。
补充验证小实验
如果你修改第一个例子,把列表推导式拆成4行手动添加协程:
coros = [] coros.append(some_async_func()) # 行1 coros.append(some_async_func()) # 行2 coros.append(some_async_func()) # 行3 coros.append(some_async_func()) # 行4
这时候每个协程来自不同代码行,单独启用这个例子会触发4次警告,和第二个例子的行为一致——这也印证了“代码位置是警告去重的关键因素”。
python version: 3.12.8
备注:内容来源于stack exchange,提问作者Kitaram
相关产品推荐
相关产品推荐

