ExceptionGroup能一次性暴露Web请求中多个子任务的全部异常。它原生结构化返回所有失败原因,支持except*语法按类型分流处理,add_note()可附加请求上下文,且不依赖列级错误定位。

Web请求中多个子任务失败时,ExceptionGroup能一次性暴露所有问题
传统 Web 视图或 API 处理函数里,如果并行调用多个下游服务(比如同时查数据库、调第三方 API、读缓存),旧版本 Python 只会抛出第一个异常,其余失败被掩盖。Python 3.11 的 ExceptionGroup 让你一次看到全部失败原因——不是靠日志拼凑,而是原生结构化返回。
- 典型场景:
async with asyncio.TaskGroup()启动多个httpx.AsyncClient请求,其中 2 个超时、1 个返回 401;ExceptionGroup自动打包这 3 个异常,而不是只报第一个 - 对比
asyncio.gather(..., return_exceptions=True):后者返回混合列表,你需要手动过滤isinstance(x, Exception),再聚合;ExceptionGroup直接提供.exceptions属性和except*语法支持 - 注意:必须用
async with asyncio.TaskGroup(),不能直接 new 对象;否则会报RuntimeError: TaskGroup is not running
用 except* 按错误类型分流处理,避免 if-elif 链判断
Web 错误追踪常需区分网络层、认证层、业务逻辑层的异常,except* 允许你按类型精准捕获子异常,不用写一堆 isinstance() 或字符串匹配。
- 例如:
except* TimeoutError单独处理超时,except* HTTPStatusError处理状态码错误,except* ValueError处理 JSON 解析失败——互不干扰,顺序无关 - 如果只写
except Exception:,会完全错过ExceptionGroup,因为它是新类型,不是Exception的子类 - 在 FastAPI 或 Starlette 中,可以在全局异常处理器里用
except* ConnectionError统一加监控埋点,而不用侵入每个路由逻辑
add_note() 给异常附加请求上下文,调试时不用翻日志
Web 请求失败时,光看 KeyError: 'user_id' 没用,你得知道这是哪个用户、哪个 endpoint、哪个 trace_id 下出的问题。add_note() 能把运行时上下文直接挂到异常对象上,随 traceback 一起输出。
- 实操方式:在中间件或装饰器里捕获异常后,调用
e.add_note(f"request_id={request.state.trace_id}")、e.add_note(f"path={request.url.path}") - 这些备注不会出现在
str(e)里,只在完整 traceback 末尾显示,且位置固定(在错误消息之后、调用栈之前) - 容易踩的坑:必须在
raise e前调用add_note();如果先raise再except,原异常已离开作用域,add_note()无效
动态生成代码或模板渲染时,列级错误定位依然失效
Web 开发大量使用 Jinja、Mako 或 f-string 动态拼接代码,这时候 PEP 657 的列级标注(比如 ^^^^^ 精准指向缺失括号)会退化——但 ExceptionGroup 和 add_note() 不受影响,仍是可靠抓手。
立即学习“Python免费学习笔记(深入)”;
- 现象:
exec("data['user']['profile']")报错时,箭头可能标整行甚至错位;但如果你用TaskGroup并发执行 5 个这样的片段,仍能分清哪个片段抛了KeyError、哪个抛了SyntaxError - 建议:对模板渲染结果做预检,用
ast.parse()提前验证语法,比依赖运行时报错更可控 - 真正容易被忽略的是:
ExceptionGroup的异常堆栈默认不展开,打印时只显示ExceptionGroup('group', [...]);必须用except*或手动遍历eg.exceptions才能看到细节


















