必须用 except* 捕获 exceptiongroup 子异常,因它继承自 baseexceptiongroup 而非 exception;except valueerror 等传统语法无效,exit 中需显式遍历 exceptions,add_note 不支持 exceptiongroup 本身。

不能直接用传统 except 捕获 ExceptionGroup 里的子异常,必须用 except* 语法解包处理;否则看似“捕获成功”,实际子异常被静默吞掉。
为什么 except ValueError: 对 ExceptionGroup 无效
ExceptionGroup 和 ValueError 不在同一个继承链上——它继承自 BaseExceptionGroup,而后者不是 Exception 的子类。所以即使你 raise ExceptionGroup("oops", [ValueError("boom")]),except ValueError: 根本不会触发。
- 常见错误现象:用
asyncio.gather(..., return_exceptions=False)启动多个协程,其中两个失败(一个ValueError、一个TimeoutError),但只写except ValueError:,结果整个ExceptionGroup未被捕获,程序直接崩溃 - 真正生效的写法是:
except* ValueError as eg:—— 注意星号,且eg是解包后的子异常组,不是原始ExceptionGroup实例 - 如果想同时处理多种子异常,可以并列写:
except* ValueError as eg1:和except* TimeoutError as eg2:,它们互不干扰
在 contextlib 或自定义上下文管理器中使用 ExceptionGroup
上下文管理器的 __exit__ 方法签名是 def __exit__(self, exc_type, exc_value, traceback),它接收的是「顶层异常」。当协程批量失败抛出 ExceptionGroup 时,exc_type 就是 ExceptionGroup,exc_value 是该实例本身。
- 你不能在
__exit__里靠isinstance(exc_value, ValueError)判断——得先检查isinstance(exc_value, BaseExceptionGroup),再手动遍历exc_value.exceptions - 更安全的做法是:在
__exit__中不直接处理子异常类型,而是把exc_value重新抛出,交由外层的except*处理。否则容易漏掉部分子异常或破坏异常结构 - 如果必须在上下文内做差异化响应(比如记录不同错误类型的数量),请用
for e in getattr(exc_value, "exceptions", []):显式迭代,避免依赖except*语法(因该语法只能用于语句层级,不能用于函数内部逻辑)
add_note 能否给 ExceptionGroup 加备注
不能。add_note 是 BaseException 的实例方法,而 ExceptionGroup 继承自 BaseExceptionGroup(非 BaseException 子类),所以调用 eg.add_note("debug info") 会报 AttributeError。
- 可行替代方案:构造时在组名里塞信息,例如
ExceptionGroup("fetch_batch[uid=123]", [e1, e2]),这个字符串会出现在 traceback 首行 - 若需为每个子异常单独加 note,必须在它们被 raise 前或在
except*块中对每个e单独调用e.add_note(...) - 注意:子异常加了 note 后,仍属于原
ExceptionGroup,traceback 渲染时会一并显示,但组本身无 note 属性
最易被忽略的一点:ExceptionGroup 的生命周期和子异常强绑定——一旦你从 eg.exceptions 中取出某个子异常并重新 raise,它就脱离了组结构,后续再用 except* 就捕获不到了。需要保持组结构完整时,别提前解包或重抛单个子异常。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











