Python await 与 yield from 的区别

1次阅读

await只能在async函数中使用,因其是协程调度原语,依赖事件循环接管控制权;yield from是生成器委托语法,仅转发迭代,不触发异步等待,二者语义与接口契约完全不同,不可互换。

Python await 与 yield from 的区别

await 只能在 async 函数里用,yield from 不能直接替代它

很多人想用 yield from 模拟 await,结果报 SyntaxError: 'yield' inside async function 或运行时卡死。根本原因是:二者语义不同。await 是协程调度原语,依赖事件循环接管控制权;yield from 是生成器委托语法,只做迭代转发,不触发异步等待。

典型错误场景:把 async def 函数里一个 await asyncio.sleep(1) 换成 yield from asyncio.sleep(1) —— 直接语法报错,因为 asyncio.sleep() 返回的是 Future 对象,不是可迭代对象,yield from 根本无法消费它。

  • await 后面必须是 awaitable(coroutineFuture、实现了 __await__ 的对象)
  • yield from 后面必须是 iterator(或支持 __iter__ / __next__ 的对象)
  • 混用会导致类型错误或 TypeError: cannot 'yield from' a coroutine Object

yield from 在 asyncio 里基本没用武之地

除非你手动写生成器驱动的协程调度器(比如 python 3.4 以前的 @asyncio.coroutine + yield from 风格),否则在现代 Python(3.5+)中,yield fromasync/await 属于两套不兼容的异步机制。

常见误解是“yield from 能让协程暂停”,但它只是把子生成器的产出逐个 yield 出来,并不移交事件循环控制权。真正让出 CPU 的是 await + 事件循环的 awaitable 对象(如 asyncio.sleep()await reader.read(1024))。

立即学习Python免费学习笔记(深入)”;

  • Python 3.5 引入 async/await 后,@asyncio.coroutineyield from 已被标记为 deprecated
  • yield from 包裹 asyncio.sleep()awaitable 会抛 TypeError
  • 想调试协程执行流?别用 yield from 插桩,改用 asyncio.create_task() + 日志,或 asyncio.current_task()

await 的实际行为取决于对象的 __await__ 方法

await 不是魔法,它只是调用对象的 __await__ 方法并迭代其返回的 iterator。这个 iterator 每次 yield 一个 Awaitable,直到结束,事件循环才继续推进当前协程。

所以你能 await 一个自定义类,只要它实现 __await__ 并返回有效 iterator:

class MyAwaitable:     def __await__(self):         yield  # 让出一次控制权         return "done" <p>async def f(): result = await MyAwaitable()  # 合法</p>

yield from 完全不关心 __await__,它只认 __iter__。这也是为什么不能互换——接口契约完全不同。

  • await x 等价于 x.__await__().__next__()(简化理解)
  • yield from x 等价于 for i in x: yield i
  • 第三方库若返回 Futurecoroutine,只能 await,不能 yield from

迁移旧代码时最容易漏掉的兼容性陷阱

@asyncio.coroutine + yield from 改成 async/await 时,最常踩的坑不是语法,而是隐式状态泄漏:旧风格里生成器帧对象可能保留局部变量引用,导致内存不释放;新风格下协程对象生命周期更清晰,但如果你在 async def 里意外用了 yield(非 yield from),函数就变成生成器了,调用它不会返回协程对象,而是生成器对象——然后你 await 它,立刻报 TypeError: object XXX can't be used in 'await' expression

  • 检查所有 async def 函数体内是否混入了 yield(哪怕只有一行)
  • 旧代码里 yield from asyncio.wait(...) 必须改成 await asyncio.wait(...),且注意 asyncio.wait() 返回值结构变了(现在返回 (done, pending),不再是 generator)
  • pylintmypy 开启 await-never-in-async-def 类检查项,能提前发现 yield 混用

异步的本质不在关键字,在控制流移交时机。写错一个 yield,整个协程就降级成同步生成器,而且错误往往延迟到 await 那一刻才暴露。

text=ZqhQzanResources