Python源码中yield关键词是如何工作的 探索生成器的底层实现

爱谁谁
发布: 2025-08-03 13:40:02
原创
560人浏览过

yield 关键词使函数变为生成器,返回生成器对象而非立即执行;2. 每次调用next()时从上次暂停处恢复执行并记住局部变量和指令位置;3. python通过pygenobject和pyframeobject在c层保存/恢复状态实现暂停与恢复;4. yield from委托子生成器,自动转发next/send/throw/close并捕获返回值,简化协作。这使得生成器具备记忆能力、低内存开销及高效协同特性,远超普通函数的一次性执行模式。

Python源码中yield关键词是如何工作的 探索生成器的底层实现

yield
登录后复制
关键词在 Python 源码层面,实际上是将一个普通的函数“变身”为一个可以暂停和恢复执行的特殊迭代器——也就是我们常说的生成器。它工作的核心机制在于,当函数体中出现
yield
登录后复制
语句时,Python 解释器并不会立即执行整个函数,而是返回一个生成器对象。每当你对这个生成器对象调用
next()
登录后复制
方法时,函数会从上次
yield
登录后复制
暂停的地方继续执行,直到遇到下一个
yield
登录后复制
语句,再次暂停并“吐出”一个值。这种暂停-恢复的能力,是 Python 在 C 语言层面巧妙地保存和恢复函数执行状态(包括局部变量、指令指针、甚至整个栈帧上下文)的结果。

Python源码中yield关键词是如何工作的 探索生成器的底层实现

生成器在底层,说白了,就是 Python 提供的一种高效、内存友好的迭代方式。它不像列表那样一次性把所有数据都加载到内存里,而是按需生成数据。这对于处理大量数据流、无限序列或者需要进行复杂计算但又不想一次性占用太多资源的情况,简直是神来之笔。

生成器与普通函数有何本质区别

在我看来,生成器和普通函数最根本的区别在于它们的“生命周期”和“记忆能力”。普通函数一旦执行完毕,它的所有局部变量、执行上下文就都销毁了,下次再调用,一切从头开始。这就像你走进一个房间,做完一件事就出来了,下次再进去,你还得从门口开始。

立即学习Python免费学习笔记(深入)”;

Python源码中yield关键词是如何工作的 探索生成器的底层实现

但生成器不一样。当一个包含

yield
登录后复制
的函数被调用时,它不会立即执行函数体,而是返回一个生成器对象。这个对象,你可以把它想象成一个“被冻结的函数执行现场”。每次你对它调用
next()
登录后复制
(或者通过
for
登录后复制
循环隐式调用),它就像被“解冻”了一样,从上次
yield
登录后复制
暂停的地方继续执行。更厉害的是,它不仅能记住自己停在了哪里(指令指针),还能记住所有局部变量的当前值。这就像你走进房间,做到一半被叫出去了,但你把所有工具和做到一半的活儿都留在原地,下次回来可以直接从上次离开的地方继续。这种“记忆”和“暂停/恢复”的能力,是普通函数所不具备的,也是
yield
登录后复制
魔法的核心。

从内存角度看,普通函数往往需要一次性计算并返回所有结果(比如一个大列表),这可能导致内存爆炸。生成器则不然,它每次只生成一个值,用完就丢,极大地降低了内存占用,尤其适合处理无限序列或海量数据。

Python源码中yield关键词是如何工作的 探索生成器的底层实现

Python解释器如何管理生成器的内部状态?

要理解 Python 解释器是如何管理生成器内部状态的,我们需要稍微深入到 CPython 的实现细节。这背后主要涉及两个关键的 C 结构体:

PyGenObject
登录后复制
PyFrameObject
登录后复制

当你调用一个生成器函数时,Python 解释器并不会执行函数体内的代码,而是立即创建一个

PyGenObject
登录后复制
实例。这个
PyGenObject
登录后复制
内部有一个非常重要的字段,通常是一个指向
PyFrameObject
登录后复制
的指针。
PyFrameObject
登录后复制
就是 Python 运行时用来表示一个函数调用栈帧的结构,它包含了函数的所有运行时信息,比如局部变量、参数、代码对象、以及最重要的——当前执行到的字节码指令位置(通常由
f_lasti
登录后复制
字段表示)。

初次创建

PyGenObject
登录后复制
时,它内部的
PyFrameObject
登录后复制
只是一个骨架,还没有真正被激活执行。当第一次调用生成器的
next()
登录后复制
方法时,解释器会激活这个
PyFrameObject
登录后复制
,将其推入 C 语言的调用栈,然后开始执行生成器函数体内的字节码。

uBrand Logo生成器
uBrand Logo生成器

uBrand Logo生成器是一款强大的AI智能LOGO设计工具。

uBrand Logo生成器 57
查看详情 uBrand Logo生成器

执行过程中,一旦遇到

yield
登录后复制
语句,解释器会做几件事:

  1. 它会记录下当前
    PyFrameObject
    登录后复制
    的执行状态,特别是
    f_lasti
    登录后复制
    (下一条要执行的指令的偏移量)以及所有局部变量的当前值。
  2. 然后,它会将这个
    PyFrameObject
    登录后复制
    从 C 语言的调用栈中“弹出”,但并不会销毁它,而是将其继续保留在
    PyGenObject
    登录后复制
    内部。
  3. 最后,它会将
    yield
    登录后复制
    后面的值作为
    next()
    登录后复制
    方法的返回值抛出。

当再次调用

next()
登录后复制
时,解释器会从
PyGenObject
登录后复制
中取出之前保存的
PyFrameObject
登录后复制
,根据
f_lasti
登录后复制
记录的偏移量,直接跳转到上次暂停的地方,并恢复所有局部变量的值,然后继续执行。这个过程会一直重复,直到生成器函数执行完毕(或者遇到一个不带值的
return
登录后复制
语句),这时解释器会抛出
StopIteration
登录后复制
异常,表示迭代结束。

这种设计非常巧妙,它利用了现有的栈帧机制,但又通过

PyGenObject
登录后复制
实现了栈帧的“冻结”和“解冻”,从而实现了函数的暂停和恢复。

yield from 语句如何提升生成器协同工作的效率?

yield from
登录后复制
语句,在我看来,是 Python 3.3 引入的一个语法糖,但它远不止是糖那么简单,它为生成器之间的协作提供了一种优雅而强大的机制。它主要用于委托子生成器,将迭代器的大部分操作(
next()
登录后复制
send()
登录后复制
throw()
登录后复制
close()
登录后复制
)直接转发给子生成器处理,直到子生成器耗尽或返回一个值。

yield from
登录后复制
出现之前,如果你想在一个生成器里迭代另一个生成器,你可能需要写这样的代码:

def sub_generator():
    yield 1
    yield 2
    return "Sub done" # 返回值在老方法中很难直接获取

def main_generator_old():
    for value in sub_generator():
        yield value
    # 如何获取 sub_generator 的返回值?需要额外的try-except StopIteration逻辑
登录后复制

这不仅代码冗余,而且更重要的是,子生成器的返回值(通过

return
登录后复制
语句返回的值)在外部是很难直接获取的,你必须捕获
StopIteration
登录后复制
异常才能拿到。此外,
send()
登录后复制
throw()
登录后复制
close()
登录后复制
等方法也需要手动转发,非常麻烦。

yield from
登录后复制
彻底改变了这一切。它提供了一种透明的委托机制:

def sub_generator():
    yield 1
    yield 2
    return "Sub done" # 这个返回值会被 yield from 捕获

def main_generator_new():
    result = yield from sub_generator() # 直接委托给 sub_generator
    print(f"Sub generator finished with: {result}")
    yield 3

gen = main_generator_new()
print(next(gen)) # 输出 1
print(next(gen)) # 输出 2
print(next(gen)) # 输出 "Sub generator finished with: Sub done",然后输出 3
登录后复制

yield from
登录后复制
的核心在于它建立了一个双向通道:

  1. 数据流向: 子生成器
    yield
    登录后复制
    出来的值,会直接传递给委托生成器的调用者。
  2. 控制流向: 委托生成器的调用者通过
    next()
    登录后复制
    send()
    登录后复制
    throw()
    登录后复制
    close()
    登录后复制
    等方法发送给委托生成器的值或异常,会直接转发给子生成器。
  3. 返回值捕获: 当子生成器通过
    return
    登录后复制
    语句返回一个值时,这个值会被
    yield from
    登录后复制
    表达式捕获,成为
    yield from
    登录后复制
    表达式的求值结果。

这种直接委托机制,极大地简化了复杂生成器链的编写,使得代码更加清晰、易读。它不仅解决了返回值的问题,还自动处理了

StopIteration
登录后复制
异常的传播,以及
send()
登录后复制
throw()
登录后复制
方法的转发,让多个生成器之间的协同工作变得如同调用普通函数一样自然。这在异步编程(如
asyncio
登录后复制
中的
async
登录后复制
/
await
登录后复制
,它们在底层就大量依赖
yield from
登录后复制
的机制)中尤为关键,因为它为协程的嵌套和组合提供了坚实的基础。

以上就是Python源码中yield关键词是如何工作的 探索生成器的底层实现的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号