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

相关推荐
Alan GEO实施教练2 小时前
实用新型专利申请代理机构选择:关键考量因素与实操要点讲解
java·开发语言·python
闲云lazycloud2 小时前
08-Java工程师的Python第八课-框架入门
python
是真的小外套2 小时前
第十一章:Flask入门之从零构建Python Web应用
前端·python·flask
Westward-sun.2 小时前
NLP 词向量实战:PyTorch 从零实现 CBOW(Word2Vec)全流程拆解
人工智能·pytorch·python·深度学习·自然语言处理·word2vec
青瓷程序设计2 小时前
基于YOLO的安全帽佩戴检测系统~Python+模型训练+2026原创+YOLO算法
python·算法·yolo
badhope2 小时前
2025年3月AI领域纪录:从模型开源到智能体价值重估——风云变幻DLC
人工智能·python·深度学习·计算机视觉·数据挖掘
小陈工2 小时前
Python Web开发入门(一):虚拟环境与依赖管理,从零搭建纯净开发环境
开发语言·前端·数据库·git·python·docker·开源
七夜zippoe3 小时前
联邦学习实战:隐私保护的分布式机器学习——联邦平均与差分隐私
分布式·python·机器学习·差分隐私·联邦平均
不懒不懒3 小时前
【OpenCV 计算机视觉四大核心实战:从背景建模到目标跟踪】
人工智能·python·opencv·机器学习·计算机视觉