ExceptionGroup是Python 3.11引入的专用于聚合并发异常的不可变容器,继承自BaseException而非Exception,因此普通except Exception无法捕获;必须用except*语法解包匹配子异常,并保留各异常原始traceback。

ExceptionGroup 不是锦上添花,而是解决异步并发中“异常丢失”这个根本问题的唯一机制。
ExceptionGroup 是什么,为什么普通 except Exception 捕不到它
ExceptionGroup 是 Python 3.11 引入的内置异常类型,专门承载多个并发任务同时失败时聚合的异常。它不是装饰性包装,而是一个结构化容器:内部用 .exceptions 属性存一个元组,每个元素都是原始异常实例。
- 普通
except Exception:完全匹配不到ExceptionGroup,因为它的类型是ExceptionGroup,不是Exception的子类(它是平行于BaseException的新分支) - 必须用
except<em></em>语法才能解包,比如except ValueError:或except* BaseException: - 如果你写
except Exception:去捕TaskGroup抛出的异常,代码会直接崩溃,报Uncaught ExceptionGroup
except* 怎么用,和普通 except 有啥本质区别
except* 是模式匹配式捕获:它不找“第一个匹配”,而是把 ExceptionGroup 里所有满足条件的异常都挑出来,组成一个新的 ExceptionGroup 继续抛,没被匹配的异常则留在原组里继续向上冒泡。
-
except* httpx.HTTPStatusError:→ 只捕获其中所有HTTPStatusError,其余异常(比如TimeoutError或ConnectionError)仍保留在外层组中 -
except* BaseException:→ 捕获全部,等价于“兜底” - 不能混用
except和except*在同一个try块里,否则语法错误
示例:
立即学习“Python免费学习笔记(深入)”;
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(fetch("https://a.com"))
tg.create_task(fetch("https://b.com"))
tg.create_task(fetch("https://c.com"))
except* httpx.HTTPStatusError as eg:
print(f"HTTP 错误共 {len(eg.exceptions)} 个")
except* TimeoutError as eg:
print(f"超时 {len(eg.exceptions)} 次")
except* BaseException as eg:
print(f"其他未分类异常 {len(eg.exceptions)} 个")为什么不用 ExceptionGroup 就没法做可靠类任务异常处理
类任务(如封装了 httpx.AsyncClient、数据库连接、文件句柄的协程)失败时,往往不是“单点错误”,而是“状态污染”。比如:
- 一个上传协程中断,临时文件没删干净
- 一个事务协程崩溃,没 rollback,连接池卡在 dirty 状态
- 多个任务共享一个 client 实例,一个任务 close 了 client,其余任务直接
RuntimeError: <client> is closed
TaskGroup 遇到首个异常时,会广播 cancel 并等待所有任务走完 async with / finally 清理逻辑,最后才把所有已发生的异常聚合成 ExceptionGroup 抛出。这意味着:
- 你必须面对“多个异常同时存在”的事实,不能假装只发生了一个
- 无法靠
task.result()拿结果来规避——只要有一个失败,整个组就终止,其余任务即使成功也得等清理完才能取值 - 如果某个任务死循环不响应 cancel,
async with会卡住,直到超时或它自己退出
真正难的不是语法,是接受“并发异常不可简化为单一错误”这个前提。



















