必须用 except 解包 exceptiongroup,因它不继承 exception,普通 except 无法捕获子异常;except 可直接匹配子异常类型并获取完整 traceback。

必须用 except* 解包,不能用普通 except 捕获子异常
Python 3.11 的嵌套异常(比如 asyncio.TaskGroup 中多个子任务失败)会聚合成一个 ExceptionGroup,它不继承自 Exception,所以 except ValueError: 或 except Exception: 根本捕不到里面的 ValueError。你看到的永远是顶层的 ExceptionGroup 实例,而不是你真正想处理的具体错误。
常见错误现象:
- 写了
except httpx.HTTPStatusError:,但TaskGroup抛出异常时完全不进入该分支 - 用
str(eg)打印异常,只看到摘要,看不到每个子异常的完整 traceback - 试图用
isinstance(exc, ValueError)判断ExceptionGroup实例,结果恒为False
正确做法是用 except* 语法直接匹配子异常类型:
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(fetch_user(1))
tg.create_task(fetch_user(2))
tg.create_task(fetch_user(3))
except * httpx.HTTPStatusError as eg:
print(f"HTTP 错误共 {len(eg.exceptions)} 个")
for exc in eg.exceptions:
print(f" → {exc.response.status_code}")
except * asyncio.TimeoutError as eg:
print(f"超时共 {len(eg.exceptions)} 个")
注意:eg.exceptions 是一个列表,每个元素都是原始抛出的子异常实例,可直接调用 .args、检查 .response 等属性。
如何保留完整 traceback 并做结构化日志
ExceptionGroup 的字符串表示太简略,调试时根本不够用。直接 print(eg) 只显示 “ExceptionGroup: load failed (2 sub-exceptions)”,连哪一行出错都看不到。
要输出可读、可追溯的错误信息,必须用 traceback.print_exception():
import traceback <p>try:</p><h1>...</h1><p>except * ValueError as eg: traceback.print_exception(eg) # 这会逐个打印每个子异常的完整 traceback </p>
如果你在用 logging,推荐这样写:
import logging
import traceback
<p>logger = logging.getLogger(<strong>name</strong>)</p><p>try:</p><h1>...</h1><p>except * ValueError as eg:
logger.error("批量校验失败", exc_info=eg) # 注意:传的是 eg,不是 sys.exc_info()
</p>
关键点:
-
exc_info=eg而不是exc_info=True,后者只取当前活跃异常(即ExceptionGroup本身,不含子异常) -
traceback.print_exception(eg)是唯一能展开全部子异常 traceback 的标准方式 - 不要手动遍历
eg.exceptions再对每个调用traceback.print_exception(exc)—— 这会丢失组结构和顶层消息
嵌套 TaskGroup 场景下,异常如何向上聚合?
当内层 async with asyncio.TaskGroup() 抛出 ExceptionGroup,而外层也有 TaskGroup 时,异常不会被“吞掉”,而是自动合并进外层的 ExceptionGroup。
例如:
async def inner():
async with asyncio.TaskGroup() as tg:
tg.create_task(bad_io()) # OSError
tg.create_task(bad_parse()) # ValueError
<p>async def main():
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(inner())
except <em> OSError as eg:
print("IO 类错误", len(eg.exceptions))
except </em> ValueError as eg:
print("解析类错误", len(eg.exceptions))
</p>
这时,inner() 抛出的 ExceptionGroup("inner failed", [OSError, ValueError]) 会被外层 TaskGroup 接收,并和它自己的其他子任务异常一起打包成新的 ExceptionGroup。最终 except * OSError 会捕获到那个 OSError 子异常,except * ValueError 同理。
这意味着:
- 无需在每层都做
try/except*,外层统一处理即可 - 异常传播路径清晰,不会因嵌套丢失类型信息
- 但要注意:如果某层用了普通
except Exception:吞掉了ExceptionGroup,后续就再也解包不了了
最易被忽略的一点:ExceptionGroup 不是“多个异常的列表”,它是带语义的容器——顶层消息(如 "load failed")和子异常的上下文共同构成完整错误叙事。跳过 except* 直接用传统方式处理,等于主动放弃这个结构,把并发失败降级成不可靠的模糊提示。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











