ExceptionGroup 不是 Exception 的子类,故 except Exception 无法捕获;必须用 except* 语法才能正确匹配并处理其子异常,保留各 traceback 与上下文。

ExceptionGroup 不是 Exception 的子类,普通 except 捕获不到
Python 3.11 中 ExceptionGroup 直接继承自 BaseException,而非 Exception。这意味着 except Exception: 完全无法匹配它——哪怕组内全是 ValueError 或 TimeoutError,也会直接向上冒泡,导致未处理异常崩溃。
常见错误现象:ExceptionGroup: 2 exceptions 报错后程序退出,日志里只看到顶层包装,看不到具体哪几个子任务失败、各自 traceback 是什么。
-
except Exception:→ 无效,永远进不去 -
except BaseException:→ 虽能捕获,但失去类型区分能力,和裸写except:几乎等价 - 真正有效的入口只有
except*语法
asyncio.gather 默认就抛 ExceptionGroup,不是可选项
在 Python 3.11+ 中,asyncio.gather() 在 return_exceptions=False(默认)时,只要任意一个协程出错,就会把**所有已发生的异常**打包成 ExceptionGroup 抛出——不再是旧版“只抛第一个”。这是行为变更,不是配置开关。
你不需要手动构造,也不需要额外依赖;只要用了 gather 并且没设 return_exceptions=True,就已经在和 ExceptionGroup 打交道了。
- 不改捕获逻辑 → 程序在并发失败时必然 crash
- 误用
except ValueError:→ 匹配失败,ExceptionGroup继续上抛 - 正确写法必须是
except* ValueError:,星号不能漏
except* 能保留每个子异常的原始 traceback 和上下文
except* 不是“挑一个异常出来”,而是从 ExceptionGroup 的 exceptions 元组中筛选出所有匹配类型的子异常,并把它们重新打包成一个新的 ExceptionGroup 绑定到变量。原始 traceback、局部变量、调用栈全部保留。
这让你能做真正有意义的分诊:比如只重试 ConnectionError,记录 ValueError 为业务问题,忽略 CancelledError。
- 一个
except* ConnectionError as eg:块可同时拿到多个连接失败实例 - 子异常之间互不影响,未被匹配的仍保留在原组中,留给后续
except*处理 - 不能混用
except和except*,否则报SyntaxError: cannot mix 'except' and 'except*'
自定义异常要能被 except* 捕获,关键在 __init__ 实现
你写的 class MyApiError(Exception): 只要没破坏 args 结构,就能被 ExceptionGroup 安全包裹和 except* MyApiError: 正确匹配。
最容易踩的坑是重写了 __init__ 却忘了调用 super().__init__(...),导致 self.args 为空——这时 ExceptionGroup 构造可能静默跳过该异常,或在 except* 中因无 message 而难以调试。
- ✅ 正确:显式调用
super().__init__(msg),确保self.args非空 - ❌ 危险:在
__init__里raise ValueError("bad arg")→ExceptionGroup构造直接失败 - ⚠️ 无关紧要:重写
__str__或__repr__不影响匹配,只影响日志可读性
ExceptionGroup 的核心价值不在“多抛几个异常”,而在于让并发失败变成可诊断、可分流、可追溯的结构化数据。漏掉星号、混用 except、忽略 return_exceptions 行为变化——这三个点,是上线后最常导致告警失焦和故障定位延迟的原因。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











