news 2026/4/27 1:29:27

Python 3.12 Key Words - 13 - yield

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 3.12 Key Words - 13 - yield

Python 3.12 Key Words -yieldyield from


生成器是 Python 中一种特殊的迭代器,它允许你延迟生成数据,而不是一次性将所有结果存储在内存中。yield关键字用于定义生成器函数,而yield from则用于将迭代任务委托给子生成器。生成器在流式处理大型数据集、实现协程(Python 3.5 之前)、以及构建无限序列时具有不可替代的地位。本文将深入解析yieldyield from的语法、语义、使用场景、底层实现,并通过大量示例逐行讲解,帮助你彻底掌握生成器的奥秘。


第一部分:生成器基础 ——yield

1.1 什么是生成器?

生成器是一种特殊的迭代器,它通过yield语句返回值,并记住当前执行状态,待下次调用时从上次暂停的地方继续执行。

普通函数 vs 生成器函数

  • 普通函数使用return返回值,调用后函数结束,局部变量被销毁。
  • 生成器函数使用yield返回值,调用后返回一个生成器对象,函数体暂停,局部变量保留。
defsimple_generator():print("Start")yield1print("Resume")yield2print("End")

1.2 基本语法

defgenerator_func():yieldvalue# 可以多个 yield

调用生成器函数返回一个生成器对象,支持迭代协议(__iter____next__)。

1.3 示例与逐行解析

defcount_up_to(n):i=0whilei<n:yieldi i+=1# 创建生成器对象gen=count_up_to(3)print(next(gen))# 0print(next(gen))# 1print(next(gen))# 2print(next(gen))# StopIteration

逐行解析

  • def count_up_to(n):定义生成器函数。
  • yield i:暂停函数,将i返回给调用者,并记住当前位置(包括局部变量i的值和循环状态)。
  • gen = count_up_to(3):调用生成器函数,返回生成器对象(不会立即执行函数体)。
  • next(gen):执行生成器代码直到遇到第一个yield,返回0,函数暂停。
  • 再次next(gen),从上一次yield之后继续执行,循环体增量,再次yield,返回1,暂停。
  • 当循环结束,函数自然退出,再调用next(gen)会抛出StopIteration

为什么这样写?
生成器能够在需要时逐个产生值,避免一次性构建整个列表,节省内存。特别适合处理大文件、无限序列或流式数据。

1.4 生成器对象的方法

生成器对象提供了几个方法:

  • __next__():等价于next(gen),执行到下一个yield
  • send(value):向生成器内部发送一个值,该值成为当前yield表达式的返回值,然后继续执行到下一个yield
  • throw(type, value=None, traceback=None):在生成器当前挂起的位置抛出异常。
  • close():关闭生成器,在内部引发GeneratorExit异常。

这些方法使生成器可以作为协程使用(现代 Python 用async/await替代,但仍有应用)。

使用send
defecho():whileTrue:received=yieldprint(f"Received:{received}")g=echo()next(g)# 启动生成器,执行到第一个 yield(此时 yield 返回 None)g.send("Hello")# 发送 "Hello",yield 接收该值并赋值给 received,然后打印,循环继续g.send("World")
使用throw
defcatcher():try:yield1yield2exceptValueError:yield3g=catcher()print(next(g))# 1print(g.throw(ValueError))# 3 (异常被捕获,yield 3)
使用close
defgen():try:yield1finally:print("Cleanup")g=gen()print(next(g))g.close()# 触发 finally,打印 "Cleanup"

1.5 生成器表达式

生成器表达式类似于列表推导式,但使用圆括号而不是方括号,返回一个生成器对象,惰性求值。

squares=(x*xforxinrange(10))print(next(squares))# 0print(next(squares))# 1

生成器表达式适合在需要逐个处理数据且只需迭代一次的场景。


第二部分:yield from—— 委托生成器

2.1 为什么需要yield from

在 Python 3.3 之前,如果要让一个生成器产生另一个可迭代对象的所有值,需要手动循环:

defflatten(nested):forsubinnested:foriteminsub:yielditem

yield from可以简化这种嵌套循环,并支持向子生成器发送值、传递异常等高级功能。

2.2 基本语法

defgenerator():yieldfromiterable

等价于:

defgenerator():foriteminiterable:yielditem

yield from更加高效,且能处理子生成器的返回值。

2.3 示例与逐行解析

defsubgen():yield1yield2return"Done"defdelegator():result=yieldfromsubgen()print(f"Result from subgen:{result}")g=delegator()print(next(g))# 1print(next(g))# 2try:next(g)# 子生成器结束,返回 "Done",赋值给 result,打印,然后 delegator 结束,抛出 StopIterationexceptStopIteration:pass

逐行解析

  • subgen是一个简单的生成器,产生12,最后return "Done"
  • delegatorresult = yield from subgen()
    • 首先,subgen()被调用,返回一个生成器对象。
    • yield from会迭代该生成器,并将subgen产生的值逐个转发给delegator的调用者。
    • subgen结束时,它的return值("Done")被捕获,并赋值给result,然后delegator继续执行后面的print
    • 最后delegator正常结束,抛出StopIteration
  • 外部代码调用next(g)三次:前两次获取12,第三次触发delegator完成,打印并抛出StopIteration

为什么这样写?
yield from不仅简化了嵌套循环,还能传递sendthrowclose调用,实现生成器间的双向通信,是编写复杂数据处理管道的基础。

2.4 递归遍历树结构

defflatten_tree(tree):ifisinstance(tree,(list,tuple)):forsubtreeintree:yieldfromflatten_tree(subtree)else:yieldtree nested=[1,[2,[3,4],5],6]foriteminflatten_tree(nested):print(item)# 1 2 3 4 5 6
  • yield from递归地将子列表中的元素逐一产生,简洁高效。

2.5 作为协程的双向通道

yield from可以与子生成器进行双向通信:父生成器通过send发送的值会传递给子生成器,子生成器通过yield返回的值会传递给父生成器的调用者。

defsubgen():whileTrue:received=yieldprint(f"Subgen received:{received}")defdelegator():yieldfromsubgen()g=delegator()next(g)# 启动,执行到子生成器的第一个 yieldg.send("Hello")# 父生成器将值传递给子生成器g.send("World")

2.6 异常传播

如果子生成器抛出异常,该异常会传播到父生成器。如果父生成器捕获了异常,可以继续处理。

defsubgen():yield1raiseValueError("Oops")yield2defdelegator():try:yieldfromsubgen()exceptValueErrorase:print(f"Caught:{e}")g=delegator()print(next(g))# 1next(g)# 子生成器抛出异常,被 delegator 捕获,打印,然后 delegator 结束

第三部分:生成器的底层实现

3.1 生成器对象的结构(CPython)

在 CPython 中,生成器对象由PyGenObject结构表示:

typedefstruct{PyObject_HEAD PyFrameObject*gi_frame;// 帧对象,保存执行状态PyObject*gi_code;// 代码对象char*gi_running;// 是否正在运行PyObject*gi_weakreflist;// 弱引用列表PyObject*gi_name;PyObject*gi_qualname;}PyGenObject;
  • gi_frame:保存当前执行帧(包括局部变量、字节码指针等)。
  • gi_code:生成器函数的编译后代码对象。
  • gi_running:防止生成器递归调用。

3.2 生成器的执行流程

  1. 调用生成器函数时,Python 创建PyGenObject实例,但不立即执行函数体。
  2. 第一次调用next(gen)时,解释器调用gen_send_ex函数,开始执行生成器函数的字节码。
  3. 当遇到yield指令时,解释器保存当前栈帧的位置,将返回值压栈,并挂起生成器。
  4. 下一次调用next(gen)恢复帧,从上次yield之后继续执行。
  5. 当函数正常返回或抛出异常时,生成器结束,并抛出StopIteration

3.3 字节码分析

importdisdefsimple_gen():yield1yield2dis.dis(simple_gen)

输出(简化):

2 0 LOAD_CONST 1 (1) 2 YIELD_VALUE 4 POP_TOP 3 6 LOAD_CONST 2 (2) 8 YIELD_VALUE 10 POP_TOP 12 LOAD_CONST 0 (None) 14 RETURN_VALUE
  • YIELD_VALUE:将栈顶的值产出,并挂起生成器。
  • 当生成器恢复时,从YIELD_VALUE之后的指令继续。

3.4yield from的字节码

yield from对应GET_YIELD_FROM_ITERYIELD_FROM指令。它比手动循环更高效,因为它在 C 层面实现了子迭代器的迭代逻辑,并处理sendthrow等。


第四部分:综合应用示例

4.1 无限序列:斐波那契数列

deffibonacci():a,b=0,1whileTrue:yielda a,b=b,a+b fib=fibonacci()for_inrange(10):print(next(fib),end=' ')# 0 1 1 2 3 5 8 13 21 34

4.2 分块读取大文件

defprocess(data):passdefread_in_chunks(file_path,chunk_size=1024):withopen(file_path,'rb')asf:whileTrue:chunk=f.read(chunk_size)ifnotchunk:breakyieldchunkforchunkinread_in_chunks('large_file.bin'):process(chunk)# 每次处理一块,内存友好

4.3 管道处理(生成器链)

defread_file(file):withopen(file)asf:forlineinf:yieldline.strip()deffilter_lines(lines,keyword):forlineinlines:ifkeywordinline:yieldlinedefcount_lines(lines):cnt=0forlineinlines:cnt+=1yieldcnt pipeline=count_lines(filter_lines(read_file('data.txt'),'error'))total=next(pipeline)print(total)

4.4 使用yield from展平嵌套列表

defflatten(lst):foriteminlst:ifisinstance(item,list):yieldfromflatten(item)else:yielditem lst=[1,2,[3,4],[[5,6],[7,8]],9]foriteminflatten(lst):print(item,end=" ")

4.5 实现简单的itertools.chain

defchain(*iterables):foritiniterables:yieldfromit lists=[[1,2],[3,4],[5,6]]# 使用 * 将列表中的每个子列表作为单独参数传入result=list(chain(*lists))print(result)# [1, 2, 3, 4, 5, 6]

4.6 生成器与协程:计算移动平均值

defaverager():total=0count=0average=NonewhileTrue:term=yieldaverage total+=term count+=1average=total/count avg=averager()next(avg)# 启动print(avg.send(10))# 10.0print(avg.send(20))# 15.0print(avg.send(30))# 20.0

4.7 使用yield from结合send

defsub_averager():total=0count=0average=NonewhileTrue:term=yieldaverage total+=term count+=1average=total/countdefdelegator():whileTrue:result=yieldfromsub_averager()print(f"Subgenerator ended with average:{result}")# 正常不会执行,因为 sub_averager 无限循环

第五部分:生成器与异步编程的关系

在 Python 3.5 之前,协程是通过生成器实现的:@asyncio.coroutine装饰器配合yield from。现代 Python 使用async/await,但底层机制仍然依赖生成器。

对比:

特性生成器协程(旧)async/await(新)
装饰器@asyncio.coroutine无需装饰器
暂停yield fromawait
类型生成器对象协程对象(CO_COROUTINE
目的实现异步 I/O相同,但语法更清晰

尽管现在推荐使用async/await,但理解yieldyield from对于阅读旧代码和底层库仍然重要。


第六部分:常见陷阱与最佳实践

6.1 生成器只能遍历一次

gen=(xforxinrange(3))print(list(gen))# [0,1,2]print(list(gen))# []

6.2 忘记关闭生成器导致资源泄露

如果生成器使用了外部资源(如文件),应使用with或显式close

6.3yieldreturn的混合

在生成器中,return会触发StopIteration,并可以携带返回值(Python 3.3+)。

defgen():yield1return42# 最终 StopIteration 的 value 是 42g=gen()print(next(g))# 1try:next(g)exceptStopIterationase:print(e.value)# 42

6.4 谨慎使用send前必须启动生成器

必须先用next(g)g.send(None)将执行推进到第一个yield

6.5yield from不能与return一起使用?

实际上可以,子生成器的return值会变成yield from表达式的值,如之前的例子。


第七部分:总结

关键字作用主要用途
yield定义生成器,返回值并暂停惰性求值、无限序列、流式处理
yield from将迭代委托给子生成器展平嵌套结构、构建管道、双向通信
  • 生成器是 Python 中实现惰性求值、内存友好型迭代的基础工具。
  • yieldyield from不仅适用于简单的序列生成,还能构建复杂的数据处理管道和协程(旧式)。
  • 理解生成器的底层实现(帧对象、字节码)有助于编写高性能代码。
  • 生成器是 Python 迭代器的终极形态,掌握它能让你的代码更加 Pythonic。

在掌握了生成器之后,我们将进入 Python 3.10 以来的重磅特性:结构模式匹配(match/case)以及 Python 3.12 引入的类型别名语法(type)。

如果在学习过程中遇到问题,欢迎在评论区留言讨论!

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

ASP Folder:深入解析ASP文件夹在Web开发中的应用

ASP Folder:深入解析ASP文件夹在Web开发中的应用 引言 ASP(Active Server Pages)文件夹是Web开发中一个非常重要的组成部分。它不仅方便了开发者的工作,而且对于提高网站性能和用户体验也具有重要意义。本文将深入探讨ASP文件夹在Web开发中的应用,包括其功能、优势以及注…

作者头像 李华
网站建设 2026/4/27 1:18:40

SVM中拉格朗日乘数法与松弛变量的应用原理

1. 拉格朗日乘数法在支持向量机中的理论基础支持向量机&#xff08;SVM&#xff09;作为机器学习领域的重要分类算法&#xff0c;其核心数学工具正是拉格朗日乘数法。当训练数据线性不可分时&#xff0c;我们需要引入松弛变量&#xff08;slack variables&#xff09;来处理分类…

作者头像 李华
网站建设 2026/4/27 1:18:14

玩转 Python:多线程、装饰器、视觉检测与正则匹配实战

Python 作为一门简洁又强大的编程语言&#xff0c;在多线程编程、函数增强、计算机视觉、文本处理等多个领域都有着广泛的应用。本文将结合几个实用的代码案例&#xff0c;带你上手 Python 的多线程、装饰器、OpenCV 颜色检测和正则表达式匹配&#xff0c;从基础应用到实际场景…

作者头像 李华
网站建设 2026/4/27 1:05:18

网球发球动作及发力指导

网球发球动作及发力指导 本文将系统讲解网球发球(Serve)的完整技术动作与发力原理,适用于初中级球员自学或教练教学参考。 目录 发球概述与技术分类 准备姿势与握拍 发球动作四阶段分解 动力链与发力原理 平击、上旋与切削发球 常见错误与纠正方法 针对性训练计划 核心要点总…

作者头像 李华
网站建设 2026/4/27 1:04:20

刚开始做 GEO:最容易做错的动作与起步误区拆解

GEO 起步阶段&#xff0c;不建议先按“发多少内容、测多少平台、截多少图”做验收。 更合适的第一轮目标是&#xff1a;固定一批真实问题&#xff0c;检查公开材料能不能被 AI 正确组织成回答。讲不准&#xff0c;先修材料&#xff1b;讲得泛&#xff0c;先补边界&#xff1b;讲…

作者头像 李华