yield from 在异步生成器中的使用规则与普通生成器的区别

async def 函数中不能用 yield from 委托异步生成器,会报 SyntaxError;正确方式是 async for 遍历再 yield,以兼容 aiter__/__anext 协议并支持取消。

yield from 在 async def 函数里直接报错

async def 定义的是协程函数或异步生成器,但 yield from 本身不支持 await 行为,所以不能直接在 async def 函数中使用 yield from 委托另一个异步生成器。Python 会抛出 SyntaxError: 'yield from' inside async function

这是因为 yield from 是同步委托协议(基于 __iter__send()),而异步生成器走的是 __aiter__ / __anext__ 协议,二者不兼容。

  • 想委托另一个异步生成器,必须用 async for + yield
  • 如果被委托对象是普通同步生成器,yield from 可以用,但整个函数不能是 async def(否则类型混用会出逻辑问题)
  • 混合使用同步/异步生成器需格外小心——比如在 async defyield from 同步生成器,虽然语法允许,但会阻塞事件循环

异步生成器中委托另一个异步生成器的正确写法

必须显式迭代并逐个 yield,即用 async for 遍历目标异步生成器,再用 yield 转发值。这是唯一符合语义且不破坏异步流的方式。

async def inner():
    yield 1
    yield 2

async def outer(): async for x in inner(): # ✅ 正确:用 async for 拉取 yield x # ✅ 再 yield 出去

  • 不能写成 yield from inner() —— 语法错误
  • 不能写成 yield await inner() —— inner() 返回的是异步生成器对象,不是可 await 的协程
  • 如果需要提前终止委托(如收到 cancel),async for 会自动调用 aclose(),这点比手动管理更安全

yield from 在普通生成器中能用,但和异步场景语义完全不同

def 定义的同步生成器里,yield from 是语法糖,等价于手动 for + yield,并自动处理 StopIterationthrow()close() 等委托逻辑。

def sync_inner():
    yield 'a'
    yield 'b'

def sync_outer(): yield from sync_inner() # ✅ 合法,且会透传 close()/throw()

  • 它背后调用的是被委托对象的 __iter__send(),全程同步阻塞
  • 若误把异步生成器传给 yield from(如 yield from async_gen()),运行时会报 TypeError: 'async_generator' object is not iterable
  • 即便包装成 awaitable,也无法绕过协议不匹配的根本限制

性能与控制流差异:为什么不能“自动适配”

异步生成器的每次 yield 都对应一次 await __anext__() 调用,涉及事件循环调度;而 yield from 的委托是纯用户态栈操作,无调度开销。两者调度粒度和错误传播路径完全不同。

  • async for + yield 多一层协程帧,但保证了 await 点清晰、取消信号可捕获
  • 试图用 loop.run_in_executor 包装同步 yield from 来“桥接”,会导致事件循环被阻塞,完全违背异步初衷
  • CPython 目前没有提供类似 yield from awaitable 的语法糖,也不太可能加——协议层级差异太大

真正容易被忽略的是异常传播:在 async for 块中,GeneratorExit 会变成 AsyncGeneratorExit,且 throw() 不再可用,只能靠 aclose()

或取消任务来中断。