生成器的 send 方法
send 是生成器对象的一个方法,它允许向生成器内部发送一个值,该值会成为当前 yield 表达式的返回值。通过 send,生成器不仅可以向外产出值,还能从外部接收输入,实现双向通信(类似于简单的协程)。
1. send 的基本用法
def echo():
while True:
received = yield # 通过 yield 接收外部发送的值
print(f"收到: {received}")
g = echo()
next(g) # 必须先用 next(g) 或 g.send(None) 启动生成器
g.send("Hello") # 发送 "Hello",输出 "收到: Hello"
g.send("World") # 发送 "World",输出 "收到: World"
关键点:
yield作为表达式时,会返回通过send发送的值。- 生成器首次执行时,需要先执行一个
next(g)或g.send(None),让它运行到第一个yield处暂停,否则无法发送数据(会报错)。
2. send 与 next 的对比
| 方法 | 作用 | 能否传值 | 返回值 |
|---|---|---|---|
next(g) | 推进生成器到下一个 yield | 否(相当于 g.send(None)) | 下一个 yield 产出的值 |
g.send(value) | 推进生成器,并将 value 作为当前 yield 的返回值 | 是 | 下一个 yield 产出的值 |
等价关系:next(g) 等价于 g.send(None)。
3. 示例:使用 send 实现一个累加器(协程)
def accumulator():
total = 0
while True:
value = yield total # 产出当前总和,并等待接收新值
if value is None:
break
total += value
acc = accumulator()
next(acc) # 启动,运行到 yield total,返回 0
print(acc.send(5)) # 发送 5,total 变成 5,yield 返回新 total → 输出 5
print(acc.send(3)) # 发送 3,total 变成 8,输出 8
print(acc.send(2)) # 发送 2,total 变成 10,输出 10
acc.send(None) # 发送 None,触发 break,生成器结束(抛出 StopIteration)
这个例子中,生成器不断接收数值并累加,每次 send 后产出最新的总和。
4. 必须“启动”生成器的原因
生成器函数不会立即执行,调用时只返回生成器对象。第一次执行 next(g) 或 g.send(None) 会使代码运行到第一个 yield 处暂停。如果直接调用 g.send(value),由于没有 yield 等待接收值,会抛出 TypeError: can't send non-None value to a just-started generator。
5. 高级用法:throw 和 close
生成器还提供:
g.throw(exc_type):在生成器当前暂停点抛出异常。g.close():向生成器内部抛出GeneratorExit异常,用于清理。
6. 总结
send让生成器从“只产出”变成“既可产出又可接收”。- 它常用于实现协程(比完整
async/await更轻量级的状态机)。 - 使用时记住先启动(
next(g)或g.send(None))。 - 返回值是下一个
yield产出的值,而不是发送的值本身。
如果你已经掌握了基础生成器,send 是迈向更高级用法的关键一步。