Python yield from 的返回值:子生成器写了 return,为什么 list 里没有这份汇总

前天 3阅读

批处理生成器逐条yield记录,最后return一份计数与总和。调用者转成list后只看到记录,便以为汇总被丢了。实际上return结束生成器,并把完成值放进结束信号;普通迭代消费的项目和完成时的返回值并不是同一条通道。

下面只产生两条固定数字记录。保存为demo.py,运行python demo.py。先用list观察普通项目,再手动读取完成信号,最后通过yield from接收汇总,并由外层决定是否把汇总变成一条显式事件。

Python yield from 的返回值:子生成器写了 return,为什么 list 里没有这份汇总

AI模型生成概念示意:普通记录沿输送带逐项交出,结束汇总通过另一个出口交给接收者;不是实际任务系统截图。

def batch():
    yield ("row", 2)
    yield ("row", 5)
    return {"rows": 2, "sum": 7}

plain = list(batch())
assert plain == [("row", 2), ("row", 5)]
print("list:", plain)

direct = batch()
assert next(direct) == ("row", 2)
assert next(direct) == ("row", 5)
try:
    next(direct)
except StopIteration as finished:
    summary = finished.value
else:
    raise AssertionError("generator did not finish")
assert summary == {"rows": 2, "sum": 7}
print("return value:", summary)

def with_summary():
    result = yield from batch()
    yield ("summary", result)

events = list(with_summary())
assert events == plain + [("summary", summary)]
print("delegated:", events)

def broken():
    yield 1
    raise StopIteration("manual ending")

bad = broken()
assert next(bad) == 1
try:
    next(bad)
except RuntimeError as error:
    assert isinstance(error.__cause__, StopIteration)
    print("manual StopIteration: RuntimeError")
else:
    raise AssertionError("manual ending did not fail")

完成值不自动成为最后一项

第一行list只有两个row元组,没有字典。随后对一个新的生成器调用两次next取记录,第三次next触发StopIteration;捕获后读value,得到 {'rows': 2, 'sum': 7}。这个实验直接定位了return值的去向,也避免把第一次已经耗尽的对象误当作新输入。

普通for循环和list通过结束信号知道迭代完成,但不会自动把信号里的value加入结果。若一个接口承诺最后交出汇总记录,就应明确yield该记录或在外层重新表达,不能只在函数末尾增加return后期待所有调用者都自动看到。

委派表达式能接住子过程的汇总

with_summary中的yield from先把子生成器的两个row直接交给外层消费者,等子生成器正常结束后,整个表达式的值才赋给result。下一行再显式yield带summary标签的元组,因此delegated结果共有三项。第三项的出现来自这句显式yield,不是yield from额外复制了return。

给事件加row和summary标签,便于消费者按类型处理,避免把字典混进数字流后交给sum等函数。若汇总只供外层内部判断,外层可以使用result而不yield它;这时消费者仍然只看到两条记录。接口应选择一种明确的协议。

正常结束使用return

最后的broken故意在生成器体内直接raise StopIteration。第一次next得到一,第二次打印manual StopIteration: RuntimeError,断言确认原来的StopIteration成为原因。现代Python会把从生成器体内意外逸出的这类异常转换成RuntimeError,不能把手动抛异常当作return的等价缩写。

只有子生成器真正到达结束位置,委派表达式才会得到完成值。消费者提前停止或调用close,并不等价于完整消费后拿到最终汇总;需要保障收尾的资源仍应有自己的清理安排。此例只讨论同步生成器,不能直接把yield from搬进异步生成器。

测试时分别断言逐项结果、完成值以及外层事件协议。这样重构嵌套生成器时,既能检查数据有没有重复,也能发现汇总虽然计算出来却无人接收的情况。

资料核对日期:2026年10月2日。示例在本地 Python 3.12.14 实际执行,全部断言通过;输出对应文中固定输入。

参考资料

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