throw()是强制状态机跳转+栈帧重入的同步操作,不经过微任务队列,直接在挂起yield处注入异常并触发异常处理流程。

要真正理解 throw() 在生成器中的物理执行行为,关键不是把它看作“抛异常”,而是把它当作一次强制状态机跳转+栈帧重入的操作——它不依赖事件循环,也不走微任务队列,而是直接在当前生成器挂起的栈帧上触发异常处理流程。
throw() 不进微任务队列,它直击暂停点
微任务队列(如 Promise.then、queueMicrotask)是 JavaScript 的机制,Python 的 throw() 完全不经过任何队列调度。它同步执行:调用瞬间,解释器立即定位到生成器当前挂起的 yield 行,把异常注入该暂停点,并恢复栈帧执行——就像你手动把一张断点截图贴回调试器,然后点“继续”。
- 生成器必须已启动(至少调过一次
next()),否则没有挂起栈帧,throw()会报TypeError - 异常不是“发给生成器”,而是在 yield 表达式所在位置直接 raise,所以能被
try/except捕获,也能触发finally块 - 如果暂停点外层没有
except或finally,异常会向上冒泡,终止生成器并置为STOPPED状态
物理栈帧如何响应 throw()
当 gen.throw(TypeError("boom")) 被调用时,CPython 执行三步硬操作:
- 取出生成器对象内部保存的
f_frame(即上次yield后挂起的栈帧) - 将异常对象写入该帧的
f_exc_type/f_exc_value/f_exc_traceback字段 - 调用
PyEval_EvalFrameEx从暂停指令处继续执行——此时解释器识别到“该 yield 正在等待值”,但收到的是异常,于是跳过 yield 表达式求值,直接进入异常分发逻辑
这和 send() 的底层路径高度一致,只是控制流走向异常处理分支而非正常赋值分支。
与 yield from 委托链的联动效果
若当前生成器通过 yield from subgen 委托子生成器,throw() 会穿透委托协议,自动转发到子生成器的当前暂停点:
- 父生成器不捕获该异常 → 异常传给
subgen,由子生成器的try/except或finally处理 - 子生成器未处理 → 异常沿委托链逐级向上传递,最终在父生成器中冒泡
- 子生成器主动
return或耗尽 → 父生成器的yield from表达式收到StopIteration,不中断异常传播
这种穿透能力正是 asyncio 中 Task.cancel() 能精准中断嵌套协程的底层支撑。
为什么不能用 asyncio 的 cancel 替代 throw()
Task.cancel() 最终会调用 coro.throw(CancelledError),但它多了一层封装:
- 先标记任务为
CANCELLED状态 - 再检查协程是否处于
WAITING(即挂起在await上),才触发throw() - 若协程正在 CPU 密集运算中,
cancel()只是设标记,直到下次await才生效
而原生 gen.throw() 是无条件、即时、绕过所有调度逻辑的物理干预——它不关心你在 await 还是算 π,只要栈帧挂着,就能捅进去。











