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这条路,基础知识要打扎实,想输出记录一下,如果有错的地方欢迎指正~

相关推荐
vortex53 小时前
从Conda到UV:Python项目依赖管理的现代演进
python·conda·uv
何以解忧,唯有..3 小时前
Python time 模块详解:时间处理与日期操作指南
开发语言·python
梅雅达编程笔记4 小时前
Day 18 · 综合实战 B:AI 客服 Agent(专栏收官)
python·智能客服·ai agent·意图识别·ai客服·大模型应用·ai办公自动化
测试老哥4 小时前
Pytest自动化测试框架tep环境变量、fixtures、用例三者之间的关系
自动化测试·软件测试·python·测试工具·测试用例·pytest·职场发展
Buke..4 小时前
【APP 逆向】哔哩哔哩 sign 参数逆向(下):OLLVM 混淆还原 sign 算法
java·javascript·爬虫·python·算法
JacksonMx4 小时前
Java CompletableFuture 异步编程实战:从入门到架构师避坑指南
linux·数据库·python
W_326005 小时前
Python-OpenCV HSV颜色空间与inRange颜色分割:按颜色提取目标物体
图像处理·人工智能·python·opencv·机器学习
Buke..5 小时前
【APP 逆向】哔哩哔哩 sign 参数逆向(上):Frida 反调试绕过与 unidbg 调用
java·开发语言·爬虫·python·安卓
头发够用的程序员6 小时前
ImportError: libopenblas.so.0: cannot open shared object file 完整复盘与解决方案
python·ubuntu
梦痕长情6 小时前
记一次关于AI在数仓中的应用
开发语言·人工智能·python·ai