Python yield 原理

yield 是 Python 中一个关键的关键字,它让一个普通函数转变为生成器函数 ,调用时返回一个生成器对象。生成器对象实现了迭代器协议,支持惰性求值,可以暂停执行并在之后恢复,从而实现高效的流式处理、协程等功能。

1. 从普通函数到生成器函数

当一个函数体中包含 yield 语句时,Python 解释器不会把它当作普通函数处理:

  • 普通函数 :调用时执行函数体,遇到 return 返回值,函数栈帧在返回后被销毁。

  • 生成器函数 :调用时不执行函数体 ,而是立即返回一个生成器对象 。函数体实际执行发生在对生成器对象调用 __next__()(或 next())时。

    python 复制代码
    def my_gen():
        print("start")
        yield 1
        print("resume")
        yield 2
    
    g = my_gen()          # 不输出任何内容,返回生成器对象
    next(g)               # 输出 "start",得到 1
    next(g)               # 输出 "resume",得到 2
    next(g)               # 抛出 StopIteration

2. 生成器如何保存状态

生成器能够"暂停"和"恢复"的秘密在于它的执行状态被保存

  • 当生成器执行到 yield 时,当前栈帧(包含局部变量、指令指针、异常状态等)被从调用栈上弹出,但不会被销毁,而是保存在生成器对象的内部。

  • 下一次调用 next() 时,生成器将之前保存的栈帧重新压入调用栈,并从 yield 之后的下一条指令继续执行。

这种机制使得生成器可以记住所有局部变量、循环状态等,就像从未中断过一样。

在 CPython 实现中,生成器对象有一个 gi_frame 属性指向栈帧对象,该栈帧在堆上分配,因此可以独立于调用栈生存。

3. 迭代器协议

生成器对象是迭代器,因为它实现了:

  • __iter__():返回自身。

  • __next__():恢复执行,直到遇到下一个 yield 并返回其值,如果没有更多值则抛出 StopIteration

因此生成器可以直接用在 for 循环等期望迭代器的地方。

python 复制代码
def even_until(n):
    i = 0
    while i <= n:
        yield i
        i += 2

for x in even_until(10):
    print(x)   # 0 2 4 6 8 10

4. yield 的增强功能:send()、throw()、close()

生成器不仅仅是迭代器,还可以作为协程 使用,通过 yield 表达式接收外部传入的值。

  • send(value) :恢复生成器,并将 value 作为 yield 表达式的返回值。第一次启动生成器必须用 next()send(None)

  • throw(type, value=None, traceback=None):在生成器暂停点抛出异常。

  • close() :向生成器内部抛出 GeneratorExit 异常,用于清理资源。

    def coroutine(): x = yield 0 print(f"received: {x}")

    c = coroutine() next(c) # 运行到 yield,返回 0 c.send(42) # 将 42 赋给 x,打印 "received: 42",然后生成器结束

5. 内存与性能优势

  • 惰性求值:生成器按需生成值,不需要一次性将所有数据载入内存,适合处理大文件、无限序列等。

  • 栈帧复用:生成器内部栈帧在堆上分配,生命周期与生成器对象绑定,避免了普通函数多次调用带来的栈分配/释放开销(尽管每次恢复仍有开销,但总体更适合流式场景)。

6. 底层实现简述

在 CPython 中,生成器对象是 PyGenObject 类型,主要字段包括:

  • gi_frame:指向 PyFrameObject,保存执行状态。

  • gi_code:指向函数代码对象。

  • gi_running:表示生成器是否正在执行(防止递归调用)。

当一个生成器被 next() 时,解释器通过 _PyEval_EvalFrameDefault() 恢复执行栈帧。遇到 yield 时,将返回值存入生成器对象的 gi_exc_state,然后保存当前栈帧状态,并切换回调用者。

7. 总结

  • yield 使函数成为生成器函数,调用返回生成器对象。

  • 生成器对象保存了执行状态(栈帧),可以在多次 next() 之间暂停与恢复。

  • 生成器实现了迭代器协议,并支持 send()throw()close() 等协程特性。

  • 核心优势是惰性求值与低内存占用,适用于处理大数据流、无限序列以及实现轻量级协程。

理解 yield 的原理,有助于编写高效、可读的流式代码,也能更深入地理解 Python 的迭代器和协程模型。最近在搞python ai agent这条路,基础知识要打扎实,想输出记录一下,如果有错的地方欢迎指正~

相关推荐
码界筑梦坊27 分钟前
282-基于Python的豆瓣音乐可视化分析推荐系统
开发语言·python·信息可视化·数据分析·flask·vue
LJianK127 分钟前
java多态
java·开发语言·python
_Evan_Yao28 分钟前
栈与队列:后进先出与先进先出的智慧
开发语言·python
J2虾虾30 分钟前
Spring AI Alibaba - Skills 技能
人工智能·python·spring
带派擂总1 小时前
Python全栈开发 Day08_控制文件指针移动 异常捕获 推导式
python
XLYcmy1 小时前
面向Agent权限系统的快速审计工具
python·网络安全·ai·llm·飞书·agent·字节跳动
范范@1 小时前
Python进阶 多线程、生成器与协程
python
SilentSamsara1 小时前
SQLAlchemy 2.x:异步 ORM 与数据库迁移 Alembic 完整指南
开发语言·数据库·python·sql·青少年编程·oracle·fastapi
27669582922 小时前
京东随机变速滑块拼图验证码识别(京东E卡)
java·服务器·前端·python·京东滑块·京东变速滑块·京东e卡绑卡
weixin_468466852 小时前
支持向量机新手实战指南
人工智能·python·算法·机器学习·支持向量机