news 2026/6/14 20:21:00

19-生成器不只是省内存(下)-send双向通信与yield-from原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
19-生成器不只是省内存(下)-send双向通信与yield-from原理

文章目录

  • 生成器不只是省内存(下):send() 双向通信与 yield from——你的生成器也可以接收数据
    • 导入语
    • 1 ~> `send()`——生成器的双向通道
      • 1.1 `yield` 的两种方向
      • 1.2 图解 send() 的双向流动
      • 1.3 第一个 `send()` 前必须先 `next()`
    • 2 ~> 实战:可暂停的平均值计算器
    • 3 ~> `yield from`——委托子生成器
      • 3.1 什么是 `yield from`
      • 3.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():value=yield1# ← yield 能把右边的值"送出去"……print(f"收到:{value}")# ← 也能把左边的值"接进来"

这就是send()做的事情。上篇讲数据往外流,下篇讲数据往里流——双向通信。以及yield from的"代理"机制——它能把你对父生成器的操作(send/throw/close)透明地转发给子生成器。


1 ~>send()——生成器的双向通道

1.1yield的两种方向

defbidirectional():x=yield"第一次"# ← yield 右侧:送出去(返回值)print(f"收到:{x}")# ← yield 左侧:接进来(send 的参数)y=yield"第二次"print(f"收到:{y}")g=bidirectional()print(next(g))# 启动生成器,停在第一个 yield 处 → 输出 "第一次"print(g.send(100))# 把 100 推进去 → 左边 x=100 → 打印"收到: 100" → 继续到第二个 yield → 输出 "第二次"

yield在这行的作用:

  • 右侧:返回值给调用者(next()send()的返回值)
  • 左侧:接收调用者推送的值(send(100)→ 赋给x

1.2 图解 send() 的双向流动

调用者 生成器内部 g.send(100)───→[]→ x=yield"出"───→[]→ 返回值由调用者拿到 ↑ 这就是双向通信的位置

1.3 第一个send()前必须先next()

g=bidirectional()g.send(100)# ❌ TypeError: can't send non-None value to a just-started generator

生成器刚创建时停在函数开头——还没到第一个yieldsend(100)想把 100 赋给yield左边的变量,但此刻没有yield可以赋值。必须先调一次next()让生成器运行到第一个yield(暂停点),然后send()才能赋值。

或者g.send(None)——等价于next(g),但只能在首次启动时用。


2 ~> 实战:可暂停的平均值计算器

defrunning_average():"""维护一个运行中的平均值——可以随时注入新值并取回当前平均值"""total=0.0count=0average=NonewhileTrue:x=yieldaverage# 暂停,等待接收新值 → 返回当前平均值ifxisnotNone:total+=x count+=1average=total/count avg_calc=running_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. 平均值不变

生成器在后台保留了totalcountaverage三个局部变量。每次send()注入新数据后,立即返回更新后的平均值。


3 ~>yield from——委托子生成器

3.1 什么是yield from

yield from subgen等效于:

foriteminsubgen:yielditem

但它的真正力量不在"简化代码",而在双向转发——把调用者对这个生成器的send()throw()close()转发到子生成器。

3.2 双向转发的完整含义

defwrapper():yieldfromsubgen()# 等同于:defwrapper():g=subgen()result=NonewhileTrue:try:# 1. 启动/恢复子生成器 → 拿到 yield 值received=yieldresult# 2. 把外部 send() 的值转发给子生成器result=g.send(received)exceptStopIterationase:# 3. 子生成器结束了 → return 的值从 e.value 拿出break

当你在父生成器上调用send(val)时:

  1. yield from拿到val
  2. 把它传给子生成器的send(val)
  3. 把子生成器的yield值作为父生成器的返回值

3.3 双向转发场景示例

defsubgen():whileTrue:x=yieldprint(f"子生成器收到:{x}")defmain_gen():yieldfromsubgen()g=main_gen()next(g)# 启动到子生成器中的 yield 处g.send("hello")# 通过父生成器把 "hello" 转发给子生成器# 输出:子生成器收到: hello

4 ~>yield from还能处理return

defsubgen_with_return():yield1yield2return"完成"defparent():result=yieldfromsubgen_with_return()print(f"子生成器返回值:{result}")g=parent()print(next(g))# 1print(next(g))# 2# 第三次 next() → 子生成器结束 → StopIteration caught → result = "完成"# → print("完成")

思考 && 总结

生成器的双向通信三要点:

  1. send(x)把值推入生成器。传入的值成为当前暂停yield左边的赋值对象。首次启动先用next()send(None)
  2. yield from是透明代理。send/throw/close三种调用都被转发到子生成器。子生成器的return值通过StopIteration.value传给父生成器。
  3. 生成器的帧对象在堆中持久化——正是因为这个特性,send()能在暂停和恢复之间传递数据。

结尾

生成器上下篇到此完结。感谢两位一路读到这里的朋友!

源码骑士 — 源码级拆解,从底层看透技术

👀关注:跟博主一起从源码视角深耕底层原理

❤️点赞:让优质内容被更多人看见

收藏:核心知识点存好,随用随查

💬评论:分享你的经验或疑问,一起交流

🔄一键四连:别忘了给博主一键四连!

🗡️寄语:生成器的真核在于帧的挂起与恢复——pause & resume。

结语:生成器不是内存优化工具——它是 Python 对"可中断执行单元"的抽象。掌握它,asyncio 和异步编程才会一通百通。下一篇文章是第二板块的收官之作——手写一个迷你 Python 解释器!一键四连!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/14 20:20:03

Apache Tika 教程

Apache Tika 是一个内容分析工具包,用于检测和提取文档中的元数据和文本内容。它支持超过1000种文件格式。1. 核心特性格式检测:自动识别文件类型(MIME)内容提取:提取纯文本内容元数据提取:获取作者、创建日…

作者头像 李华
网站建设 2026/6/14 20:19:13

英雄联盟智能助手:免费开源游戏效率工具终极指南

英雄联盟智能助手:免费开源游戏效率工具终极指南 【免费下载链接】League-Toolkit An all-in-one toolkit for LeagueClient. Gathering power 🚀. 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit 还在为英雄联盟中的繁琐操作而烦恼…

作者头像 李华
网站建设 2026/6/14 20:17:00

【 上岸必看!【药学】必背100题及解析(卷号:06121219_03) 】

【 上岸必看!【药学】必背100题及解析(卷号:06121219_03) 】 ■ 试卷元数据 更新日期:2026-06-13 涉及科目:药学、中药学、基础课 题量统计:共 90 道核心考题 ■ 内容摘要 本卷旨在帮助2026年…

作者头像 李华
网站建设 2026/6/14 20:14:23

Android字节码逆向工程架构深度解析与实战应用

Android字节码逆向工程架构深度解析与实战应用 【免费下载链接】dex2jar Tools to work with android .dex and java .class files 项目地址: https://gitcode.com/gh_mirrors/de/dex2jar dex2jar作为Android逆向工程领域的重要工具集,提供了完整的Dalvik字节…

作者头像 李华
网站建设 2026/6/14 20:13:16

Pandas DataFrame行级集合运算:交集并集差集实战

1. 项目概述:用集合思维重写 DataFrame 行级操作逻辑 “Set Operations on Python DataFrames”——这个标题乍看像教科书里的一个章节名,但在我带过二十多个数据分析项目、处理过超 300TB 跨源业务数据的实操经验里,它其实是 解决真实世界脏…

作者头像 李华