Python 生成器 send 双向传值:还没走到第一个 yield,为什么不能先送入数字
生成器不只能把数据交给调用者。写成 amount = yield total 后,暂停点还可以接收调用者送回来的值。但刚创建对象就执行 send(3),会遇到 TypeError;而已经启动以后,send(3) 返回的又不是三本身。这两个现象都要按暂停位置来解释。
下面用一个只接受整数增量的累计器演示双向交换,None 在这个小协议里表示不增加。代码只处理固定的内存数据,适用于 Python 3.8 及以上。保存为 demo.py,运行 python demo.py;不要加优化选项,以便保留教学断言。
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,全部断言通过;这是语言行为演示,不是生产压测。


