python中的`yield`、`next()`、`send()`的关系
在Python中,yield是生成器函数的“核心开关”——它不仅能让函数返回一个值,更能暂停函数执行并保存当前状态,等待下次被唤醒时从暂停处继续运行。这种“暂停-恢复”的特性,让生成器实现了“惰性求值”和“状态保持”,而next()和send()则是与生成器交互的“遥控器”,分别负责“唤醒生成器”和“唤醒并传递数据”。
一、yield的基本含义:不止是“返回”,更是“暂停”
yield最直观的作用是“在生成器函数中返回一个值”,但和return有本质区别:
return会终止函数,所有局部状态(变量、执行位置)都会被销毁;yield会暂停函数,保留所有局部状态(变量的值、当前执行到的代码行),等待下次被唤醒。
看一个简单例子:
def simple_gen():
print("开始执行")
yield 1 # 第一次暂停:返回1,保存状态
print("继续执行")
yield 2 # 第二次暂停:返回2,保存状态
print("执行结束")
# 调用生成器函数,返回生成器对象(函数体不执行)
gen = simple_gen()
print(type(gen)) # 输出:<class 'generator'>
当我们用next()唤醒生成器时,yield的“暂停-返回”特性会体现得更明显:
# 第一次调用next():启动生成器,执行到第一个yield处暂停
print(next(gen))
# 输出:
# 开始执行
# 1
# 第二次调用next():从上次暂停的yield处继续执行,到第二个yield处暂停
print(next(gen))
# 输出:
# 继续执行
# 2
# 第三次调用next():从第二个yield处继续执行,直到函数结束,抛出StopIteration
print(next(gen))
# 输出:
# 执行结束
# 报错:StopIteration
核心逻辑:yield是生成器的“断点”——每次遇到yield,函数就会“冻结”在当前行,把yield后的值返回给调用者,同时记住自己“执行到哪了”“局部变量是什么”,等下次被唤醒时,从断点处继续往下走。
二、yield的底层行为:状态保存与上下文切换
从底层看,yield的本质是触发生成器的“状态机切换”。生成器在生命周期中有以下状态(可通过gen.gi_frame.f_lasti查看执行位置,gen.gi_running查看是否运行):
- 初始化状态:生成器对象被创建,但未执行任何代码(
gi_frame.f_lasti = -1); - 运行状态:
next()/send()触发后,函数开始执行(gi_running = True); - 暂停状态:执行到
yield处,函数暂停,保存上下文(gi_frame记录局部变量和执行位置); - 结束状态:函数执行完毕,抛出
StopIteration(后续调用不再有响应)。
yield的关键作用是在“运行状态”和“暂停状态”之间切换,具体做了3件事:
- 返回值:将
yield后面的表达式结果(如yield 1中的1)返回给调用者; - 保存上下文:把当前函数的局部变量(如循环计数器、临时变量)、指令指针(下一行要执行的代码位置)保存到生成器的
gi_frame属性中; - 释放控制权:暂停函数执行,将程序控制权交还给调用者(比如
for循环或next()函数)。
用一个带循环的生成器更能看清状态保存:
def count_gen(max_num):
current = 1
while current <= max_num:
yield current # 每次暂停时,current的值会被保存
current += 1 # 恢复后从这里继续执行
gen = count_gen(3)
print(next(gen)) # 输出:1(current=1,暂停后保存current=1)
print(next(gen)) # 输出:2(恢复后current+1=2,暂停时保存current=2)
print(next(gen)) # 输出:3(恢复后current+1=3,暂停时保存current=3)
每次yield后,current的值都会被“记住”,下次恢复时能从current += 1继续,这就是yield保存状态的底层体现。
三、next()与yield:“唤醒生成器,获取下一个值”
next()是最基础的“生成器遥控器”,它的作用是唤醒处于暂停状态的生成器,让其执行到下一个yield处,并返回yield的值。如果生成器是首次被调用(处于初始化状态),next()会启动生成器,执行到第一个yield处暂停。
next()与yield的交互流程:
- 调用
next(gen)→ 生成器从当前暂停位置(或初始位置)开始执行; - 执行到
yield value→ 生成器暂停,将value作为next()的返回值; - 再次调用
next(gen)→ 生成器从上次暂停的yield处继续执行,直到遇到下一个yield,重复步骤2; - 如果执行完函数体仍未遇到
yield→ 生成器抛出StopIteration,表示迭代结束。
用流程图表示:
调用next(gen) → 生成器启动/恢复 → 执行到yield value → 返回value,暂停
↑
再次调用next(gen) ──────────────────┘
四、send()与yield:“唤醒并传递数据给生成器”
send()是比next()更灵活的交互方式——它不仅能唤醒生成器,还能向生成器的暂停位置传递一个数据,这个数据会成为上一个yield表达式的返回值。
send()与yield的交互流程:
- 生成器在
yield value处暂停(此时yield value是一个表达式,尚未返回值); - 调用
gen.send(data)→ 生成器被唤醒,同时data被赋值给上一个yield表达式(即result = yield value中的result会被设为data); - 生成器从暂停处继续执行,直到遇到下一个
yield new_value→ 暂停,返回new_value作为send()的返回值; - 若生成器执行完毕 → 抛出
StopIteration。
看一个带数据传递的例子:
def echo_gen():
print("准备接收数据")
while True:
# yield既是返回值,也是接收send数据的表达式
received = yield # 接收send传递的数据,赋值给received
print(f"收到数据:{received}")
if received == "quit":
break
yield "结束"
# 创建生成器
gen = echo_gen()
# 第一步:必须先用next()启动生成器,让其执行到第一个yield处暂停
next(gen) # 输出:准备接收数据(生成器停在received = yield处)
# 第二步:用send()传递数据,生成器被唤醒,received被赋值为"hello"
gen.send("hello") # 输出:收到数据:hello(执行到下一个yield,继续暂停)
# 继续send数据
gen.send("world") # 输出:收到数据:world
# 发送"quit"触发退出
gen.send("quit") # 输出:收到数据:quit(执行break,到yield "结束"处暂停)
# 获取最后返回值
print(next(gen)) # 输出:结束
关键细节:
- 生成器未启动时(处于初始化状态),不能用
send(data)传递非None数据(会报错),必须先用next(gen)或gen.send(None)启动,让其跑到第一个yield处; yield在这里被当作“双向通道”:向外返回值(例子中首次yield没有返回值,可理解为yield None),向内接收send()传递的数据。
五、next()与send()的关系:next()是send(None)的简化版
实际上,next(gen)本质上等价于gen.send(None)——两者都是唤醒生成器,但next()不传递数据,send()可以传递数据。
源码层面,next()的实现逻辑类似:
def next(gen):
return gen.send(None) # 调用send,传递None
这就是为什么首次调用gen.send(None)和next(gen)效果完全一致:都能启动生成器,执行到第一个yield处暂停。
六、总结:yield、next()、send()的核心关系
-
yield:生成器的“断点+双向通道”- 向外返回值(供
next()/send()获取); - 向内接收
send()传递的数据(作为自身表达式的返回值); - 暂停函数并保存状态。
- 向外返回值(供
-
next():生成器的“基础唤醒器”- 等价于
send(None),唤醒生成器并获取下一个yield的返回值; - 用于不需要向生成器传递数据的场景(如简单遍历)。
- 等价于
-
send():生成器的“增强唤醒器”- 唤醒生成器,同时向
yield表达式传递数据; - 用于需要与生成器动态交互的场景(如实时调整生成逻辑)。
- 唤醒生成器,同时向
用一句话概括:yield定义了生成器的“暂停点和数据接口”,next()和send()则是通过这个接口与生成器交互的工具——前者只“取”,后者能“存”能“取”。理解了这层关系,就能灵活运用生成器处理复杂的迭代场景(如协程、数据流处理、状态机等)。

浙公网安备 33010602011771号