asyncio.gather(return_exceptions=True) 能拿到所有结果是因为它不传播异常、不取消其余任务,而是将每个任务的返回值或异常对象原封不动存入列表,返回 List[Any]。

asyncio.gather(return_exceptions=True) 为什么能拿到所有结果
默认情况下 asyncio.gather() 遇到第一个异常就中断,其余任务被取消,你什么都拿不到;设成 return_exceptions=True 后,它把每个任务的返回值或异常对象原封不动塞进结果列表,不传播、不取消、不短路。
- 返回类型始终是
List[Any],需用isinstance(x, Exception)判断是否失败 - 任务间完全独立:一个超时,另一个仍可跑完;适合批量采集、多端点探测等“尽力而为”场景
- 注意:它不会帮你重试,也不会自动记录日志,异常只是“安静地躺在列表里”
- 常见误用:在
except ValueError:块里处理这个列表——其实根本不会进 except,因为没异常抛出来
TaskGroup 中部分失败时为什么只看到第一个异常
asyncio.TaskGroup 的设计哲学是“结构化并发”,即任一任务失败,整个组立即取消,且只暴露第一个触发的异常(包装成 ExceptionGroup),其余异常被静默丢弃——这不是 bug,是明确行为。
- 错误现象:
ExceptionGroup: 3 exceptions报错但except Exception:捕获不到,因为ExceptionGroup不是Exception的子类 - 必须用
except* ValueError:这种语法才能拿到匹配的子异常,eg.exceptions是元组,每个元素是原始异常实例 - 不能加
return_exceptions=True参数:TaskGroup 不支持该选项,这是它和gather的关键分水岭 - 适用场景:强一致性要求,比如多个数据库写入必须全成功或全回滚,不允许“部分提交”
如何在 TaskGroup 中真正拿到全部异常
靠 except* 是唯一正解,但它不是“兜底 catch-all”,而是按类型精准提取子异常。如果你有多种失败可能(比如 ValueError、TimeoutError、ConnectionError),就得写多个 except* 块。
- 顺序无关:先写
except* TimeoutError:还是except* ValueError:都行,它们各自匹配自己的子集 - 未被任何
except*匹配的异常会继续向上抛,所以别漏掉兜底的except* Exception: - 示例中若只写
except* ValueError:,而实际混着TimeoutError,那后者会直接崩溃程序 - 调试时可用
repr(eg)或traceback.print_exception(eg)看完整嵌套结构
升级 Python 3.11+ 之前怎么模拟 ExceptionGroup 行为
低版本没有 ExceptionGroup 和 except*,只能靠 asyncio.gather(return_exceptions=True) + 手动聚合异常信息来逼近效果,但本质不同:它不取消任务,也不构造异常组。
- 返回的是扁平列表,你要自己遍历、分类、组装错误摘要,比如:
{'value_errors': [...], 'timeouts': [...]} - 无法做到 TaskGroup 那样的“自动取消剩余任务”,想实现类似逻辑得手动调用
task.cancel()并 awaitasyncio.wait(..., return_when=asyncio.FIRST_COMPLETED) - 如果真需要异常组语义(比如统一上报、嵌套诊断),可以用
ExceptionGroup("batch", exc_list)手动构造,但仅用于测试或日志,不能触发except* - 最易忽略的点:低版本中,
asyncio.create_task()启动的任务若没被 await 或没加入gather,异常会静默丢失——TaskGroup 从语法上杜绝了这种漏 await
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











