<p>ExceptionGroup 是 Python 3.11 引入的必需机制,用于解决 asyncio 批量任务中多异常丢失问题:asyncio.gather 默认聚合所有异常为 ExceptionGroup,须用 except* 才能解包子异常,传统 except 无法捕获。</p>

ExceptionGroup 不是锦上添花的语法糖,而是解决 asyncio 批量任务失败时“丢异常”问题的刚需机制。Python 3.11 之前,asyncio.gather 遇到多个协程出错,只抛第一个异常,其余静默丢失;3.11 后默认用 ExceptionGroup 把所有异常打包上来——但如果你还用老式 except ValueError:,就根本捕获不到它们。
asyncio.gather 默认触发 ExceptionGroup,不是可选项
只要任意一个子协程失败,asyncio.gather(return_exceptions=False,即默认行为)就会把所有已发生的异常聚合成一个 ExceptionGroup 抛出,哪怕只有一个协程出错,也会包装成单元素组。
- 常见错误现象:
await asyncio.gather(a(), b(), c())中b()和c()都抛了TimeoutError,但你只看到ExceptionGroup: 2 exceptions,except TimeoutError:完全不进分支 -
return_exceptions=True可绕过ExceptionGroup,但代价是结果列表里混着Exception实例,你需要手动过滤和类型判断,逻辑更散乱 - 性能影响几乎为零:
ExceptionGroup构造本身开销极小,且 Python 3.11 的“零成本异常处理”机制让它在未触发时无额外负担
except* 是唯一能解包子异常的语法
except* 不匹配 ExceptionGroup 对象本身,而是直接对它内部的子异常做类型匹配——这是传统 except 永远做不到的。
- 不能和普通
except混写在同一try块里,否则报SyntaxError: cannot mix 'except' and 'except*' -
except* OSError as eg:绑定的eg是一个新的ExceptionGroup,只含原始组里所有OSError实例,eg.exceptions是 tuple,可直接遍历或取len(eg.exceptions) - 多个
except*子句按顺序执行,且互不干扰:一个匹配了部分子异常,另一个仍会对剩余未匹配的尝试匹配
捕获后不处理子异常,等于没捕获
拿到 ExceptionGroup 实例后,如果只是 print(eg) 或 raise eg,你就放弃了结构化优势。老版本 Python 甚至无法识别 ExceptionGroup,直接报 NameError。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
立即学习“Python免费学习笔记(深入)”;
- 必须显式访问
eg.exceptions,它是 tuple,不会自动递归展开嵌套的ExceptionGroup - 用
traceback.print_exception(eg)才能完整打印所有子异常的 traceback;str(eg)只显示摘要 - 若需兼容 Python 3.10 及以下,不能 import
ExceptionGroup,得用try/except ImportErrorfallback,或彻底避免构造它 -
eg.subgroup(TypeError)可提取子集,返回新的ExceptionGroup或None,适合做条件分流,比如只重试网络类异常
TaskGroup 与 except* 的配合更自然
asyncio.TaskGroup 是 asyncio.gather 的结构化替代品,它在任何子任务失败时也抛 ExceptionGroup,但语义更清晰:失败即取消其余任务,异常天然聚合。
-
except* httpx.HTTPStatusError as eg:能直接拿到所有 HTTP 错误,每个exc.response.status_code都可用,不用先 unpack 再判断类型 - 注意
TaskGroup的 cancel-on-failure 行为:你没法“跳过某个失败继续等其他”,所以异常分组处理的意义在于分类响应(如记录、告警、重试),而非恢复执行 - 调试时 Pylance 和 VS Code Python Debugger 已支持
except*断点,但需确认 Python 解释器版本 ≥3.11,否则断点无效
真正容易被忽略的是:异常组不是“多异常的快捷写法”,而是一次并发失败的语义封装。你决定用 asyncio.gather 还是 TaskGroup,本质上是在选错误传播模型;而 except* 是这个模型唯一的配套解包工具——漏掉它,就等于把结构化的错误信息又塞回黑盒。

















