<p>必须用 except* 捕获,否则只能拿到第一个异常,其余全丢;因为 TaskGroup 将子任务异常打包为 ExceptionGroup,而 except Exception: 等常规捕获无法匹配该包装层,导致未被捕获的异常被静默丢弃。</p>

必须用 except* 捕获,否则只能拿到第一个异常,其余全丢。
为什么 except Exception: 会漏掉大部分错误
TaskGroup 把所有子任务异常打包成 ExceptionGroup,不是单个异常对象。写 except ValueError: 或 except Exception: 都匹配不到这个包装层,结果是:第一个异常抛出来,其余被静默吞掉,连日志都打不出来。
- 现象:
RuntimeError: Task group was cancelled这类提示常是因为你没捕获异常,导致上下文退出时任务还在跑 - 根本原因:Python 3.11+ 的异常传播机制变了,
ExceptionGroup不继承自BaseException的常规子类链 - 验证方式:在
except*块里打印eg.exceptions,能看到元组里每个原始异常实例,包括完整 traceback
except* 怎么写才真正有用
它不是语法糖,而是专门匹配异常组中「所有符合类型」的子异常。你可以分层捕获,也可以兜底提取。
- 按类型分类:
except* httpx.HTTPStatusError as eg:→ 只拿 HTTP 错误 - 忽略某类异常(如网络抖动):
eg.subgroup((asyncio.TimeoutError,))返回新ExceptionGroup,空则为None - 兜底处理:
except* Exception as eg:拿到所有未被前面分支捕获的异常 - 注意:
eg.exceptions是元组,不能直接str(eg),要用traceback.print_exception(exc)输出细节
子任务失败后怎么取结果或值
TaskGroup 不返回结果列表,也不塞异常进返回值——它只管生命周期和异常聚合。你要自己存 Task 引用,最后调 .result()。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
立即学习“Python免费学习笔记(深入)”;
- 成功任务:
task.result()返回值 - 失败任务:
task.result()会再次抛出原始异常(已在ExceptionGroup中) - 不能在
except*外直接遍历tasks调.result(),得先确保 TaskGroup 上下文已退出(即except*块执行完) - 常见错误:在
async with块内就调task.result()→ 报InvalidStateError,因为任务还没完成
哪些场景根本不该用 TaskGroup
它坚持“全进全出”,不是万能替代品。以下情况更适合 asyncio.gather(return_exceptions=True) 或手动 create_task + wait。
- 需要“只要一个成功就立刻返回”:比如并发查缓存+DB,命中缓存就停,TaskGroup 会等全部结束
- 子任务之间无依赖、完全独立,且允许部分失败不中断流程
- 要按完成顺序消费结果(类似
as_completed):TaskGroup 不提供这种能力,得换asyncio.wait(..., return_when=asyncio.FIRST_COMPLETED) - 测试 fixture 或同步上下文中强行用:会报
RuntimeError: no running event loop,必须限定在真正的async def test_函数里
最易被忽略的一点:TaskGroup 的取消信号会穿透到子协程内部,但前提是你的协程代码里有响应逻辑——比如在 httpx.AsyncClient 请求中传 timeout=...,或在循环里定期检查 asyncio.current_task().cancelled()。否则取消只是挂起,资源不会释放。

















