Python 生成器 send 双向传值:还没走到第一个 yield,为什么不能先送入数字

前天 3阅读

生成器不只能把数据交给调用者。写成 amount = yield total 后,暂停点还可以接收调用者送回来的值。但刚创建对象就执行 send(3),会遇到 TypeError;而已经启动以后,send(3) 返回的又不是三本身。这两个现象都要按暂停位置来解释。

下面用一个只接受整数增量的累计器演示双向交换,None 在这个小协议里表示不增加。代码只处理固定的内存数据,适用于 Python 3.8 及以上。保存为 demo.py,运行 python demo.py;不要加优化选项,以便保留教学断言。

Python 生成器 send 双向传值:还没走到第一个 yield,为什么不能先送入数字

AI模型生成概念图:小型装置在暂停点接收输入,再送出新的累计状态;这是控制流比喻,不是程序运行截图。

def accumulator():
    total = 0
    while True:
        amount = yield total
        if amount is not None:
            if type(amount) is not int:
                raise TypeError("amount must be an integer or None")
            total += amount

fresh = accumulator()
try:
    fresh.send(3)
except TypeError:
    print("before first yield: TypeError")
else:
    raise AssertionError("unstarted generator accepted a value")
finally:
    fresh.close()

stream = accumulator()
try:
    outputs = [next(stream), stream.send(3), stream.send(4), next(stream)]
    assert outputs == [0, 3, 7, 7]
    print("exchanges:", outputs)
finally:
    stream.close()

try:
    stream.send(1)
except StopIteration:
    print("after close: StopIteration")
else:
    raise AssertionError("closed generator yielded again")

bad = accumulator()
next(bad)
try:
    bad.send("5")
except TypeError:
    print("invalid amount: TypeError")
else:
    raise AssertionError("text amount was accepted")
try:
    next(bad)
except StopIteration:
    print("after uncaught error: StopIteration")
else:
    raise AssertionError("failed generator remained active")

一行yield包含两个不同方向

实跑首行是 before first yield: TypeError。创建 accumulator() 只得到生成器对象,函数体还没有执行到接收位置;第一次要用 next(stream) 或 stream.send(None) 推进。启动时的 None 不是提前排队的一笔增量,它只是让函数开始运行。

第一轮推进停在 yield total,把零交给外部,amount 的赋值还没完成。下一次 send(3) 才让这个暂停中的 yield 表达式求值为三,随后赋给 amount、更新 total,再运行到下一轮 yield。因此 send 的参数是送入当前暂停点的输入,send 的返回值是下一次向外产出的结果。

next也会推进,不能当作查看按钮

exchanges 的实际输出为 [0, 3, 7, 7]。第三次交换已经累计到七,最后的 next(stream) 仍然恢复执行,并把 None 送到暂停点;因为本例明确规定 None 不增加,才再次产出七。若删除这项协议,直接 total += amount,那么同一次 next 会变成给整数加 None 并报错。

需要反复查看状态时,应由调用方保存上次产出的值,或把状态放进有查询方法的对象。不能从本例推导出所有生成器的 next 都没有副作用。送入什么类型、None 表示什么、每次会处理多少工作,必须由具体生成器的协议决定。

关闭与内部失败都会改变后续调用

close 之后 send(1) 得到 StopIteration。示例中的循环虽然没有固定终点,但调用方只做四次交换,再用 finally 显式关闭,不依赖垃圾回收时机。这不是后台线程:没有调用推进方法时,函数就停在原来的位置。

另一实例收到字符串时抛出 TypeError,该异常没有在生成器内部处理,所以它也随之结束。之后 next(bad) 输出 after uncaught error: StopIteration,而不是再次等待合格数据。若要允许重试,可以在送入前校验,或在生成器内部设计明确的错误响应;不要在外部吞掉错误后假设原状态机仍在工作。

把它用在短小、清楚的交互协议里

这类写法适合教学状态机、增量解析或需要交替交换信息的小工具。示例使用严格的 type(amount) is int,主动排除布尔值,也没有处理并发重入、无限输入或持久化。迁移时先列出启动值、允许输入、每次输出和终止方式,再为每个状态边界写一条测试,比只测连续两次正常 send 更有帮助。

资料核对日期:2026年10月2日。本文固定输入示例在本地 Python 3.12.14 实际执行,退出码为0,全部断言通过;这是语言行为演示,不是生产压测。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。