exceptiongroup 是继承自 baseexceptiongroup 的异常容器,不能用普通 except 捕获或裸 raise,须用 except* 匹配子异常;需 strict=true 或手动构造,不参与传统异常链,遍历匹配有性能与嵌套限制。

ExceptionGroup 是什么,为什么不能当普通异常用?
ExceptionGroup 不是传统意义上的单个异常,而是一个容器类型——它本身不继承自 BaseException 的子类(比如 ValueError),而是继承自 BaseExceptionGroup。这意味着你不能用 except ValueError: 捕获它,也不能直接 raise 一个裸的 ExceptionGroup 而不指定其内部异常类型。
常见错误现象:except Exception: 无法捕获 ExceptionGroup;raise ExceptionGroup("msg", [ValueError()]) 会抛出 TypeError: exceptions must be subclasses of BaseException。
- 必须显式用
except* ValueError:(注意星号)来匹配其子异常 -
ExceptionGroup的第一个参数是「入口消息」,第二个参数必须是BaseException实例列表,不能是字符串或任意对象 - 它不参与传统异常链的
__cause__或__context__传播,而是靠exceptions属性扁平化暴露子异常
在 asyncio.gather 中启用 ExceptionGroup 需要显式配置
Python 3.11 默认仍让 asyncio.gather() 在遇到多个异常时只抛出第一个(即降级为旧行为),除非你传入 return_exceptions=False(默认值)且配合 except*——但这还不够。真正触发 ExceptionGroup 的关键是启用 strict=True 参数(3.11.2+)或使用 asyncio.gather(..., return_exceptions=False) 并确保所有任务都实际抛出了异常。
使用场景:并发请求多个 API,其中部分失败,你希望一次性看到全部错误,而不是重试或掩盖。
- 错误写法:
await asyncio.gather(a(), b(), c())—— 即使三者都抛异常,也只看到第一个 - 正确写法:
await asyncio.gather(a(), b(), c(), return_exceptions=True)得到异常对象列表,但不是ExceptionGroup - 真正启用
ExceptionGroup:await asyncio.gather(a(), b(), c(), strict=True)(需 3.11.2+),失败时抛出ExceptionGroup - 兼容旧版:手动构造
ExceptionGroup("gather failed", [e1, e2]),但失去上下文关联
except* 的匹配逻辑和嵌套陷阱
except* 不是“捕获任意子异常”,而是对 ExceptionGroup.exceptions 中每个成员做独立匹配,并将匹配成功的异常组成新的 ExceptionGroup 给你。未被匹配的异常会继续向上抛出——这可能导致意外的未处理异常中断流程。
性能影响:每次 except* 都要遍历整个 exceptions 列表,若嵌套多层 ExceptionGroup(比如 ExceptionGroup("outer", [ExceptionGroup("inner", [...])])),则不会自动展开,except* ValueError 只检查顶层的直接子项。
- 嵌套
ExceptionGroup必须手动递归展开,标准库暂无flatten()工具函数 -
except* ValueError as eg:中的eg是一个新的ExceptionGroup,仅含匹配上的ValueError实例 - 若想“吞掉所有错误”,得写
except* BaseException:,但会吃掉KeyboardInterrupt等系统异常,慎用 - 与
except混用时,except*块必须放在except块之后,否则语法报错
性能优化的真实边界在哪?
ExceptionGroup 本身不提速异常抛出或捕获,它的价值在于减少「多次 try/except + 手动聚合」的样板代码,避免重复日志、重复序列化、重复网络上报。真正的性能收益来自减少异常处理路径分支和上下文切换,而非 ExceptionGroup 构造本身。
容易被忽略的地方:如果你只是把原来分散的 try/except 改成统一 except*,但没调整日志粒度或重试策略,性能几乎不变;反而可能因深度遍历 exceptions 列表引入轻微开销。
- 高吞吐服务中,避免在
except*块里做耗时操作(如 HTTP 上报、磁盘写入),应先提取关键字段再异步处理 -
ExceptionGroup的__str__是懒计算的,但首次调用会遍历全部子异常生成格式化文本,日志框架若频繁调用str(exc)可能放大开销 - 目前
traceback.print_exception()对ExceptionGroup支持尚不完善,部分 IDE 或 APM 工具可能只显示顶层消息
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











