本文目录导读:
要理解 Python 协程与生成器的底层关系,最经典的案例是 “消费者-生产者模型”,通过它你能清晰看到生成器如何一步步演化成协程。
核心关系一句话
协程 = 生成器 +
send()+yield from
生成器是协程的骨架,send()赋予了它双向通信能力,yield from实现了协程的嵌套组合。
案例:手动实现一个协程调度器(模拟操作系统)
我们先从一个普通生成器开始,逐步改成协程。
生成器(单向数据流)
def simple_gen():
"""一个普通的生成器,只能产出值"""
count = 0
while True:
yield count
count += 1
gen = simple_gen()
print(next(gen)) # 0
print(next(gen)) # 1
- 此时:生成器只能向外
yield,无法接收外部输入。 - 数据流:生成器 → 调用方(单向)。
生成器变身为协程(双向数据流)
加入 send(),生成器就能从外部接收值:
def coro_producer():
"""一个可以接收数据的生成器(即协程)"""
while True:
# 暂停并等待外部通过send()传入值
item = yield
print(f"[协程] 处理了: {item}")
c = coro_producer()
next(c) # 预激活:让协程停在yield处,准备接收数据
c.send("苹果") # 输出:[协程] 处理了: 苹果
c.send("香蕉") # 输出:[协程] 处理了: 香蕉
-
底层发生了什么?
next(c)让生成器执行直到遇到yield,挂起。c.send("苹果")将值注入到yield表达式的返回值,即item = "苹果"。- 生成器继续执行,打印后回到循环顶部,
yield挂起,等待下一次send()。
-
到此:生成器具备了协程的核心能力——可暂停、可恢复、可接收外部数据。
用 yield from 实现协程嵌套(复杂流程)
yield from 是生成器到协程的关键升级,它让协程可以委托子生成器执行,形成类似函数调用的效果。
def sub_coro():
"""子协程"""
total = 0
while True:
x = yield # 接收外部数据
total += x
print(f"子协程当前总和: {total}")
def main_coro():
"""主协程,使用yield from委托子协程"""
yield from sub_coro()
# 使用
c = main_coro()
next(c) # 预激活
c.send(10) # 子协程当前总和: 10
c.send(20) # 子协程当前总和: 30
-
底层原理:
yield from会在主协程和子协程之间建立透明管道。- 所有
send()调用会直接穿透到子协程内部的yield。 - 当子协程终止时,会向主协程返回一个值(类似函数
return)。
-
这个能力让协程能像函数一样组合,是
asyncio等框架的基础。
关键底层机制:状态机 + 栈帧
Python 的生成器/协程底层是一个函数 + 一个栈帧对象:
def demo():
a = 1
b = yield a
yield b
gen = demo()
print(gen.gi_frame.f_locals) # 查看局部变量
- 每次
yield时,Python 会保存当前栈帧的完整状态(局部变量、指令指针等)。 - 调用
next()或send()时,恢复该栈帧继续执行。 - 协程本质就是:可以多次暂停/恢复的栈帧。
从生成器到协程的完整演进图
| 特性 | 普通生成器 | 增强生成器 | 协程 |
|---|---|---|---|
| 数据方向 | 单向产出 | 可接收数据 | 双向+嵌套 |
| 核心操作 | yield |
yield + send() |
yield from |
| 暂停原因 | 产出值 | 等待输入 | 等待输入/子协程 |
| 典型用途 | 迭代器 | 简单管道 | 异步I/O、状态机 |
现实中的对比
# 生成器风格:迭代数据
def read_lines():
for line in open("file.txt"):
yield line.strip()
# 协程风格:事件循环调度
import asyncio
async def read_async():
data = await fetch_url("http://...") # await = yield from
return data
async def定义的函数,其底层代码在 Python 3.5+ 中仍会生成一个特殊的生成器对象(<class 'coroutine'>)。await在底层被翻译为yield from,只是语法糖和类型检查不同。
一句话总结
你可以把协程看作“升级版生成器”:
- 生成器是人→机器的单向送料器(
next()拿结果)。 - 协程是双向对讲机(
send()传数据,yield返回结果),并支持yield from实现子任务委派,这正是asyncio调度数百个网络请求的底层基础。