生成器的 send 方法

1 阅读2分钟

生成器的 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. sendnext 的对比

方法作用能否传值返回值
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. 高级用法:throwclose

生成器还提供:

  • g.throw(exc_type):在生成器当前暂停点抛出异常。
  • g.close():向生成器内部抛出 GeneratorExit 异常,用于清理。

6. 总结

  • send 让生成器从“只产出”变成“既可产出又可接收”。
  • 它常用于实现协程(比完整 async/await 更轻量级的状态机)。
  • 使用时记住先启动next(g)g.send(None))。
  • 返回值是下一个 yield 产出的值,而不是发送的值本身。

如果你已经掌握了基础生成器,send 是迈向更高级用法的关键一步。