news 2026/6/2 0:56:47

《流畅的Python》读书笔记19(补充01): 使用 yield from - 再谈PE380

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《流畅的Python》读书笔记19(补充01): 使用 yield from - 再谈PE380

PEP 380: Syntax for Delegating to a Subgenerator是 Python 发展史上一个里程碑式的提案,它在Python 3.3中正式引入。

核心语法是:yield from <iterable>

以下从背景痛点、架构原理、使用方法和注意事项四个维度详细解析。


1. 为什么需要引入?(解决什么痛点)

在 PEP 380 之前,如果你想在一个生成器(主生成器)中调用另一个生成器(子生成器),你需要手动处理所有交互细节。这被称为“生成器委托”的缺失。

痛点分析:
  1. 代码冗余与复杂:必须写一个for循环来遍历子生成器。
  2. 双向通信断裂:如果外部调用者使用.send(value)发送数据,或者.throw(exc)抛出异常,手动的for循环无法将这些操作透明地转发给子生成器。
  3. 返回值丢失:子生成器可以通过return value返回一个值(在 Python 3.3+ 中支持),但手动循环很难优雅地捕获这个返回值并传递给主生成器。
  4. 性能损耗:Python 层的for循环比 C 层实现的yield from慢。
解决的问题:
  • 透明代理:让主生成器像一个“透明管道”,外部调用者感觉直接在与子生成器交互。
  • 简化重构:可以将大型生成器拆分为多个小的子生成器,而无需改变外部调用逻辑。
  • 为协程奠基:它是async/await的前身,使得基于生成器的协程组合成为可能。

2. 架构与流程图解

A. 架构图:三方关系

PEP 380 自动处理区域 (透明委托)

send / next / throw

yield from

自动转发 send/throw

自动 yield 值

return 值

最终结果 / StopIteration

外部调用者
(Caller)

主生成器
(Main Generator)

子生成器
(Sub-generator)

关键点yield from建立了一个双向通道。MainGen不再仅仅是产生值,它成为了SubGenCaller之间的代理。

B. 执行流程图
Sub Gen (子)Main Gen (主)Caller (外部)Sub Gen (子)Main Gen (主)Caller (外部)初始化阶段运行阶段 (循环)loop[直到子生成器结束]结束阶段g = main_gen()next(g) / g.send(None)启动 sub_gen()yield value产出 valuesend(new_data) 或 throw(exc)转发 new_data 或 exc处理并继续 yield 或 抛出异常return result_value (触发 StopIteration)捕获 result_value抛出 StopIteration (携带 result_value)

3. 如何使用?

基本语法
result=yieldfromsub_generator()
场景一:简单委托(替代 for 循环)

旧写法 (Python < 3.3):

defchain(*iterables):foritiniterables:foriteminit:yielditem

新写法 (PEP 380):

defchain(*iterables):foritiniterables:yieldfromit

注:这里it可以是任何可迭代对象,不仅仅是生成器。

场景二:双向通信与返回值(核心威力)

这是yield from真正发挥作用的地方。

defsub_coroutine():"""子协程:接收数据,累加,最后返回总和"""total=0whileTrue:try:# 接收外部发送的值value=yieldtotalifvalueisNone:breaktotal+=valueexceptGeneratorExit:print("Sub: Cleaning up")breakreturntotal# Python 3.3+ 允许生成器返回值defmain_coroutine():"""主协程:委托给子协程"""print("Main: Starting sub-coroutine")# yield from 表达式的值就是 sub_coroutine 的 return 值result=yieldfromsub_coroutine()print(f"Main: Sub-coroutine returned{result}")returnresult# --- 测试代码 ---importsys coro=main_coroutine()next(coro)# 启动主协程,进而启动子协程# 发送数据,这些数据会透明地穿过 main_coroutine 到达 sub_coroutineprint(coro.send(10))# 输出: 10 (sub_coroutine 当前的 total)print(coro.send(20))# 输出: 30print(coro.send(30))# 输出: 60try:coro.send(None)# 发送 None 触发 sub_coroutine 的 break,进而 returnexceptStopIterationase:print(f"Final Result:{e.value}")# 输出: Final Result: 60

解析:

  1. main_coroutine中的yield from sub_coroutine()暂停了主协程,将控制权交给子协程。
  2. coro.send(10)发送给main_coroutine,但yield from自动将其转发给sub_coroutine
  3. sub_coroutineyield出的值,直接通过main_coroutine返回给外部调用者。
  4. sub_coroutinereturn total时,触发StopIteration,其value属性被赋值给main_coroutine中的result变量。

4. 需要注意什么问题?

1.StopIteration的处理
  • 当子生成器结束时,它会抛出StopIteration
  • yield from捕获这个异常,并从异常对象中提取value属性作为yield from表达式的返回值。
  • 注意:如果子生成器没有return值,yield from的结果是None
2. 异常传播
  • 如果子生成器抛出非StopIteration的异常,该异常会直接穿透yield from,抛给主生成器的调用者。
  • 如果外部调用者对主生成器调用.throw(exc),该异常会被转发给子生成器。如果子生成器没有处理该异常,它会向上传播。
3.GeneratorExit的处理
  • 当主生成器被垃圾回收或显式关闭(.close())时,GeneratorExit异常会被抛出。
  • yield from确保这个异常也被转发给子生成器,以便子生成器有机会进行清理工作(如关闭文件、网络连接等)。
4. 可迭代对象 vs 生成器
  • yield from后面可以跟任何可迭代对象(列表、元组、字符串等),不仅仅必须是生成器。
  • 但如果要跟的是普通可迭代对象,它只是简单地yield每个元素,不涉及.send()/.throw()的双向通信机制。只有当右边是一个生成器/协程时,双向通信才生效。
5. 与async/await的关系
  • 在 Python 3.5 引入async/await后,yield from在协程中的地位被await取代。
  • 最佳实践
    • 如果你写的是异步代码(async def),请使用await
    • 如果你写的是同步生成器(def+yield),并且需要委托给另一个生成器,请继续使用yield from
    • 不要在async def中使用yield from(虽然早期 asyncio 兼容,但现在已不推荐)。

总结

PEP 380 的yield from是 Python 生成器机制的“胶水”,它解决了生成器组合时的复杂性,实现了透明的双向通信和返回值传递。它是理解 Python 协程演进(从 generator-based coroutines 到 native coroutines)的关键一环。

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

保姆级攻略:用Python和MATLAB搞定2024深圳杯数学建模C题(编译器识别)

从二进制到智能分类&#xff1a;编译器版本识别的全流程实战解析当你面对一堆由不同版本GCC编译器生成的二进制文件时&#xff0c;是否曾好奇这些看似相同的机器码背后隐藏着怎样的版本指纹&#xff1f;在2024年数学建模竞赛的实战场景中&#xff0c;我们将揭开编译器识别的神秘…

作者头像 李华
网站建设 2026/6/2 0:44:55

3分钟学会B站缓存视频转换:m4s文件一键转MP4终极指南

3分钟学会B站缓存视频转换&#xff1a;m4s文件一键转MP4终极指南 【免费下载链接】m4s-converter 一个跨平台小工具&#xff0c;将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾在B站缓存了大量珍贵的…

作者头像 李华
网站建设 2026/6/2 0:43:40

如何用百度网盘API实现高效离线下载:3步自动化转存磁力链接

如何用百度网盘API实现高效离线下载&#xff1a;3步自动化转存磁力链接 【免费下载链接】baidupcsapi 百度网盘api 项目地址: https://gitcode.com/gh_mirrors/ba/baidupcsapi 还在为磁力链接和种子文件的下载而烦恼吗&#xff1f;baidupcsapi 百度网盘API Python库为你…

作者头像 李华
网站建设 2026/6/2 0:42:26

微信聊天记录永久保存终极指南:用WeChatMsg守护你的数字记忆

微信聊天记录永久保存终极指南&#xff1a;用WeChatMsg守护你的数字记忆 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we…

作者头像 李华