文章目录生成器不只是省内存下send() 双向通信与 yield from——你的生成器也可以接收数据导入语1 ~ send()——生成器的双向通道1.1 yield 的两种方向1.2 图解 send() 的双向流动1.3 第一个 send() 前必须先 next()2 ~ 实战可暂停的平均值计算器3 ~ yield from——委托子生成器3.1 什么是 yield from3.2 双向转发的完整含义3.3 双向转发场景示例4 ~ yield from 还能处理 return思考 总结结尾生成器不只是省内存下send() 双向通信与 yield from——你的生成器也可以接收数据文章简介上篇讲清楚了yield的暂停与恢复机制——生成器是持久化的帧对象。下篇进入两个进阶主题send()实现了生成器的双向通信——你不仅可以拉取数据也可以推送数据进去yield from将子生成器的双向通信委托给父生成器——实现了类似代理的效果。从源码角度解释send(x)如何将 x 注入生成器的挂起位置、yield from subgen如何处理send/throw/close三种调用的转发。配有一个真实案例——用send()实现可暂停的平均值计算器能够在运行时注入新数据并随时取出当前均值。 个人主页源码骑士❄专栏传送门《Android开发基础》《python基础课程》⭐️热衷从源码视角拆解技术底层原理将复杂架构讲得通俗易懂 源码骑士的简介5年Android Framework系统开发经验曾主导多项系统级性能优化专项技术栈覆盖Android系统全链路Binder/Handler/AMS/WMS/启动流程及Java后端全家桶Spring MyBatis Redis Oracle累计产出原创技术文章100篇文章以源码拆解为特色被读者评价为看一篇胜过啃一周文档导入语上篇我们讲完了生成器的状态机——yield暂停、next()恢复。但你有没有想过yield不仅是把数据送出去它还能 “把数据接进来”defgen():valueyield1# ← yield 能把右边的值送出去……print(f收到:{value})# ← 也能把左边的值接进来这就是send()做的事情。上篇讲数据往外流下篇讲数据往里流——双向通信。以及yield from的代理机制——它能把你对父生成器的操作send/throw/close透明地转发给子生成器。1 ~send()——生成器的双向通道1.1yield的两种方向defbidirectional():xyield第一次# ← yield 右侧送出去返回值print(f收到:{x})# ← yield 左侧接进来send 的参数yyield第二次print(f收到:{y})gbidirectional()print(next(g))# 启动生成器停在第一个 yield 处 → 输出 第一次print(g.send(100))# 把 100 推进去 → 左边 x100 → 打印收到: 100 → 继续到第二个 yield → 输出 第二次yield在这行的作用右侧返回值给调用者next()或send()的返回值左侧接收调用者推送的值send(100)→ 赋给x1.2 图解 send() 的双向流动调用者 生成器内部 g.send(100)───→[入]→ xyield出───→[出]→ 返回值由调用者拿到 ↑ 这就是双向通信的位置1.3 第一个send()前必须先next()gbidirectional()g.send(100)# ❌ TypeError: cant send non-None value to a just-started generator生成器刚创建时停在函数开头——还没到第一个yield。send(100)想把 100 赋给yield左边的变量但此刻没有yield可以赋值。必须先调一次next()让生成器运行到第一个yield暂停点然后send()才能赋值。或者g.send(None)——等价于next(g)但只能在首次启动时用。2 ~ 实战可暂停的平均值计算器defrunning_average():维护一个运行中的平均值——可以随时注入新值并取回当前平均值total0.0count0averageNonewhileTrue:xyieldaverage# 暂停等待接收新值 → 返回当前平均值ifxisnotNone:totalx count1averagetotal/count avg_calcrunning_average()next(avg_calc)# 启动生成器print(avg_calc.send(10))# 1. 当前均值... 刚好 10print(avg_calc.send(20))# 2. 当前均值... 10 20 → 平均值 15print(avg_calc.send(30))# 3. 当前均值... 15 30 → 平均值 20print(avg_calc.send(0))# 4. 平均值不变生成器在后台保留了total、count和average三个局部变量。每次send()注入新数据后立即返回更新后的平均值。3 ~yield from——委托子生成器3.1 什么是yield fromyield from subgen等效于foriteminsubgen:yielditem但它的真正力量不在简化代码而在双向转发——把调用者对这个生成器的send()、throw()和close()转发到子生成器。3.2 双向转发的完整含义defwrapper():yieldfromsubgen()# 等同于defwrapper():gsubgen()resultNonewhileTrue:try:# 1. 启动/恢复子生成器 → 拿到 yield 值receivedyieldresult# 2. 把外部 send() 的值转发给子生成器resultg.send(received)exceptStopIterationase:# 3. 子生成器结束了 → return 的值从 e.value 拿出break当你在父生成器上调用send(val)时yield from拿到val把它传给子生成器的send(val)把子生成器的yield值作为父生成器的返回值3.3 双向转发场景示例defsubgen():whileTrue:xyieldprint(f子生成器收到:{x})defmain_gen():yieldfromsubgen()gmain_gen()next(g)# 启动到子生成器中的 yield 处g.send(hello)# 通过父生成器把 hello 转发给子生成器# 输出子生成器收到: hello4 ~yield from还能处理returndefsubgen_with_return():yield1yield2return完成defparent():resultyieldfromsubgen_with_return()print(f子生成器返回值:{result})gparent()print(next(g))# 1print(next(g))# 2# 第三次 next() → 子生成器结束 → StopIteration caught → result 完成# → print(完成)思考 总结生成器的双向通信三要点send(x)把值推入生成器。传入的值成为当前暂停yield左边的赋值对象。首次启动先用next()或send(None)。yield from是透明代理。send/throw/close三种调用都被转发到子生成器。子生成器的return值通过StopIteration.value传给父生成器。生成器的帧对象在堆中持久化——正是因为这个特性send()能在暂停和恢复之间传递数据。结尾生成器上下篇到此完结。感谢两位一路读到这里的朋友源码骑士 — 源码级拆解从底层看透技术关注跟博主一起从源码视角深耕底层原理❤️点赞让优质内容被更多人看见⭐收藏核心知识点存好随用随查评论分享你的经验或疑问一起交流一键四连别忘了给博主一键四连️寄语生成器的真核在于帧的挂起与恢复——pause resume。结语生成器不是内存优化工具——它是 Python 对可中断执行单元的抽象。掌握它asyncio 和异步编程才会一通百通。下一篇文章是第二板块的收官之作——手写一个迷你 Python 解释器一键四连