作为Python异步编程(尤其是asyncio)的核心,yield from和await都用于暂停当前函数的执行并委托给另一个操作。然而,它们在控制流的本质和设计目标上存在显著差异。理解这些差异对于掌握现代Python异步模型至关重要。
1. yield from:生成器之间的委托迭代
yield from 最初(Python 3.3引入)是为了解决嵌套生成器之间的值传递、异常处理和返回值的复杂问题。它的核心作用是委托迭代(Delegate Iteration)。
在协程的早期实现中(基于生成器),yield from 被“借用”来作为暂停点和控制流的桥梁,允许一个协程调用另一个协程。
控制流本质:
- 语言层面委托: yield from iterable 会将控制权直接交给被委托的迭代器(或生成器),直到该迭代器耗尽或返回。
- 栈操作: 它是栈内的操作。当外部协程遇到 yield from 时,它会完全暂停,并且内部被委托的生成器会接管控制流。
- 不一定依赖事件循环: 它可以用于任何生成器结构,不强制要求异步环境。
def inner_generator():
yield 1
yield 2
return 'Done'
def outer_generator():
# yield from 委托给 inner_generator
result = yield from inner_generator()
print(f"Outer received: {result}")
# G = outer_generator()
# print(next(G)) # 输出 1
# print(next(G)) # 输出 2
# next(G) # 抛出 StopIteration,但 'Done' 被捕获打印
2. await:专为异步I/O设计的暂停点
await 关键字(Python 3.5引入,通过 async/await 语法)是专为原生协程设计的,用于标识一个明确的异步暂停点。它只能在 async def 定义的函数内部使用,并且只能等待一个“Awaitable”对象(如另一个协程、Future 或 Task)。
控制流本质:
- 事件循环协调: await 的核心作用是将控制权交还给事件循环(Event Loop)。
- 合作式多任务: 当一个协程遇到 await 表达式时,它会暂停执行,将执行权让出。事件循环会利用这段时间去检查是否有其他协程准备就绪(例如,等待的I/O操作已完成),并切换到执行另一个任务。
- 强制异步环境: await 强制要求当前代码运行在异步环境中,它是触发异步调度的关键。
import asyncio
async def worker(delay, name):
print(f"[{name}] 开始等待...")
# await 将控制权交给事件循环,进行非阻塞等待
await asyncio.sleep(delay)
print(f"[{name}] 等待结束。")
return f"Result:{name}"
async def main_await():
print("--- Await 示例 ---")
# await 暂停 main_await,等待 worker 完成
result = await worker(1, "Task_A")
print(f"Main收到: {result}")
# if __name__ == '__main__':
# asyncio.run(main_await())
3. 核心差异总结
虽然 await 在内部实现上最终也依赖了生成器协议(这也是为什么早期的 yield from 可以用来模拟它),但在控制流和语义上,它们的区别是根本性的:
| 特性 | yield from (基于生成器协程) | await (原生协程) |
|---|---|---|
| 主要目标 | 委托迭代和返回值/异常传递 | 标识异步暂停点,将控制权交给事件循环 |
| 暂停机制 | 栈内委托,将控制权交给被委托对象 | |
| 控制权流向 | 直接流向内部生成器 | 必须流向事件循环(Event Loop) |
| 环境依赖 | 可用于任何生成器函数 | 必须在 async def 函数中 |
本质区别:
yield from 是一种通用语言特性,用于生成器堆栈的管理和通信。
await 是一种异步专用特性,用于与事件循环通信,实现高效的合作式多任务调度。
在现代 Python 异步编程中,我们应该总是使用 async/await,它提供了更清晰的语义、更好的性能和更明确的控制流管理。
汤不热吧