exceptiongroup 是 python 3.11 引入的结构化异常容器,本质是可嵌套子异常的树状异常对象,需用 except* 或手动遍历 exceptions 属性处理,普通 except exception 无法捕获。

ExceptionGroup 是什么,和普通异常有什么本质区别
ExceptionGroup 不是多个异常的简单拼接,而是 Python 3.11 引入的结构化异常容器。它让并行任务(如 asyncio.gather、concurrent.futures)中抛出的多个异常能被统一捕获,而不是只看到第一个就中断。普通 except Exception: 捕不到它——必须显式写 except ExceptionGroup: 或用 except*(“星号捕获”)。
关键点在于:ExceptionGroup 本身是异常,但它内部可以嵌套任意数量的子异常(包括其他 ExceptionGroup),形成树状结构。这决定了你不能靠 str(e) 或 repr(e) 看清全貌,必须遍历 e.exceptions。
用 except* 捕获特定子异常类型(推荐做法)
except* 是专为 ExceptionGroup 设计的语法糖,它会对组内每个子异常独立尝试匹配,只捕获符合类型的那部分,并自动构造一个新的 ExceptionGroup 包含它们。
常见错误:写成 except ValueError: —— 这根本捕不到 ExceptionGroup 里的 ValueError 子项。
正确做法:
try:
# 假设这里触发了包含 ValueError 和 KeyError 的 ExceptionGroup
raise ExceptionGroup("batch failed", [ValueError("bad input"), KeyError("missing key")])
except* ValueError as eg:
print(f"ValueErrors: {eg.exceptions}") # eg.exceptions 是 [ValueError(...)]
except* KeyError as eg:
print(f"KeyErrors: {eg.exceptions}")
except* Exception as eg:
print(f"其他异常: {len(eg.exceptions)} 个")
注意:except* 块之间互不干扰;一个子异常可能同时匹配多个 except*(比如子类关系),但只会进入第一个匹配的块。
手动遍历 exceptions 属性做精细化处理
当你需要记录日志、聚合错误信息、或根据子异常位置做不同响应时,必须手动访问 e.exceptions。
容易踩的坑:
-
e.exceptions是 tuple,不是 list —— 不能直接.append() - 子异常可能是
ExceptionGroup,需递归检查isinstance(sub_e, BaseExceptionGroup)(Python 3.11+ 推荐用BaseExceptionGroup而非ExceptionGroup做类型判断,因前者也覆盖KeyboardInterrupt等) - 不要用
for sub in e:——ExceptionGroup不支持直接迭代,必须用e.exceptions
示例:提取所有原始错误消息并去重
def collect_messages(e):
msgs = set()
for sub in e.exceptions:
if isinstance(sub, BaseExceptionGroup):
msgs.update(collect_messages(sub))
else:
msgs.add(str(sub))
return msgs
<p>try:
raise ExceptionGroup("outer", [
ValueError("invalid age"),
ExceptionGroup("inner", [TypeError("wrong type"), ValueError("invalid age")])
])
except ExceptionGroup as eg:
print(collect_messages(eg)) # {'invalid age', 'wrong type'}
</p>
asyncio.gather 默认行为已变更,必须适配
Python 3.11 中,asyncio.gather(..., return_exceptions=False)(默认)遇到任一协程出错,会把所有异常打包进 ExceptionGroup 抛出。这是和旧版最隐蔽的兼容性断裂点。
如果你的代码此前依赖“第一个异常就终止 + 单个异常类型处理”,现在会直接崩溃(TypeError: catching classes that do not inherit from BaseException is not allowed)。
应对方式:
- 明确加
return_exceptions=True,自己检查结果列表中的Exception实例(此时不会抛ExceptionGroup) - 或改用
except*捕获,保持并发失败的语义 - 切勿在
gather外层只写except Exception:—— 它对ExceptionGroup完全无效
真正难调试的是嵌套层级深、子异常类型混杂的场景——这时候 except* 的静态类型匹配反而不如手动遍历灵活,但代价是失去语法糖的简洁性。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











