生成器函数

关注
义项:编程中的函数类型
生成器函数

生成器函数是编程语言中一类可以暂停执行、之后从暂停处恢复的特殊函数,通常通过 yield 关键字来产生值并保留局部状态。它最早出现在 CLU 语言中,20 世纪 70 年代由麻省理工学院的芭芭拉·利斯科夫等人设计的迭代器即采用这一机制。Python、JavaScript、C# 等语言都实现了生成器函数,它能以惰性方式逐个产生元素,无需一次性把全部数据放入内存,因而适合处理大规模或无限数据序列[1][2]。

百科 图文
目录
  1. 定义
  2. 原理
  3. 发展历程
  4. 应用
  5. 局限
  6. 参见

定义

生成器函数是包含 yield 语句的一类函数。和普通函数不同,调用它时函数体并不会立即执行,而是返回一个生成器对象;这个对象符合迭代器协议,可以被逐个取值,也能直接用在 for 循环中[3]。

在 Python 中,只要函数体内出现 yield,该函数就被定义为生成器函数,本质上仍是普通函数对象,只是在代码对象上带有生成器标志[3]。生成器函数与由它返回的生成器对象有时被统称为「生成器」,需要区分时才会分开称呼[3]。

不同语言对生成器函数有各自的写法。JavaScript 用 function* 声明,星号与 function 之间可以有空格[4]。Java 的生成器功能体现在迭代器与流接口上,C# 中则称为迭代器方法[5]。

生成器函数与生成器表达式的区别在于形式:前者由函数体中的 yield 定义,后者写成一对圆括号包裹的推导式,例如把列表推导式的方括号换成圆括号即可得到一个生成器[1]。两者都按需产生值,行为一致。

原理

生成器函数的核心机制是执行挂起与状态保留。当执行到 yield 时,函数状态被冻结,局部变量的绑定、指令指针和求值栈都保持原样,yield 后的表达式值返回给调用方;下次请求时函数从该点继续,就像 yield 只是一次普通的外部调用[3]。

调用方通过生成器对象的 next 方法逐步驱动函数。每次调用 next,函数体从上次暂停处开始执行,直到遇到下一个 yield;若遇到 return 或函数体结束,则抛出 StopIteration 之类的信号表示迭代完毕[6]。JavaScript 中 next 返回一个对象,value 属性是本次产出的值,done 属性表示是否结束[4]。

数据可以双向流动。除向外产出值外,调用方还能通过 send 方法向生成器内部送入值,该值成为暂停处 yield 表达式的结果;throw 方法可在暂停点抛入异常,close 方法则终结生成器[7]。具备这种双向能力后,生成器便成为一种协程,即可以在多个位置进入、退出与恢复的子程序[7]。

惰性求值是它节省内存的原因。生成器并不预先计算并保存全部结果,而是每被请求一次才计算一个值,因此内存占用与序列总长度无关,理论上可以表示无限序列[8]。与之相对,列表推导式会一次性构造完整列表,占用与元素个数成正比的存储[1]。

发展历程

生成器的思想源头是 CLU 语言的迭代器。CLU 由麻省理工学院的芭芭拉·利斯科夫和学生在 1974 年至 1975 年间设计,其迭代器以协程方式实现,每个值通过 yield 语句交给调用者[5]。这种「向调用者返回中间结果并保留自身状态,以便从断点继续」的思路后来被多种语言借鉴[3]。

Sather 的迭代器和 Icon 的生成器也提供了类似能力,Python 的生成器设计参考了这些方案[3]。在生成器出现之前,生产者与消费者之间传递多个值通常要借助回调函数或一次性构造完整列表,前者难以维护跨回调的状态,后者受限于内存[3]。

Python 的生成器由 PEP 255 引入,提案创建于 2001 年 5 月 18 日,作者为 Neil Schemenauer、Tim Peters 和 Magnus Lie Hetland,随 Python 2.2 发布[3]。最初 yield 需要通过 from future import generators 启用,随后的版本才将其变为语言关键字[3]。

此后的增强逐步扩展了生成器的能力。PEP 342 为生成器加入 send、throw 和 close 方法,使其从单向产出者变为双向的生产者与消费者,并可作为协程使用[9]。Python 2.4 引入生成器表达式,Python 3 又支持了异步生成器函数[10]。

JavaScript 在 ECMAScript 2015 标准中引入生成器函数,Firefox 31 与 Chrome 39 及更高版本原生支持[11][12]。

应用

最常见的用途是自定义迭代。用手写一个迭代器类需要维护游标状态,而生成器函数天然符合迭代器协议,几行代码就能实现嵌套结构展开、组合多个序列等遍历逻辑[13]。

处理大规模数据时,生成器可以构成流水线。多个生成器首尾相连,每个阶段只处理当前这一条数据,内存占用在各阶段都保持恒定,适合日志解析、文件逐行读取等场景[13]。相较之下,逐阶段物化中间列表会让峰值内存成倍增长[8]。

生成器也可以表达无限序列。像斐波那契数列这类没有上限的序列,用生成器逐个产出即可,无需预先确定长度,只需在消费端设置终止条件[11][14]。

在异步编程兴起的过程中,生成器曾被用作控制流的组织手段,通过把异步结果送回生成器内部,让异步代码看起来接近同步写法,从而避免层层嵌套的回调[14]。如今这类需求大多改由 async 与 await 模型处理,但生成器在定义迭代器方面仍然常用[2]。

生成器还可用于实现上下文管理。Python 的 contextlib 模块提供装饰器,让一个只 yield 一次值的生成器代替完整的类,yield 之前的代码充当进入上下文时的操作,之后的代码充当退出时的清理[15]。

局限

生成器只能单向遍历一次。序列被消费完后无法回退或重新开始,再次遍历得到的是空结果;若确实需要多次遍历,只能重新调用生成器函数或先把结果物化为列表,而后者会重新带来内存开销[8]。

它不支持随机访问和长度查询。生成器只能按顺序逐个取值,无法像列表那样用下标直接取元素,也不能事先得知元素总数[13]。

在早期 Python 中,生成器与资源清理的配合也有限制。PEP 255 规定 yield 不允许出现在 try/finally 的 try 子句中,原因在于生成器不保证一定被恢复,也就无法保证 finally 块会被执行[3]。这一点在后来的版本中才得到放宽[6]。

此外,生成器在运行期间不能被再次恢复,重复进入会引发错误[3]。异步生成器在提前退出时,其清理代码可能在不预期的上下文中运行,需要调用方显式关闭[10]。

可读性方面也存在代价。生成器的执行顺序与普通函数不同,暂停与恢复点分散在函数体中,跨多个生成器组合的调用链使得数据流向不易一眼看清[1]。

参见

  • 迭代器 —— 生成器对象符合迭代器协议,是按需产生元素的抽象。

  • yield —— 生成器函数中用来暂停执行并产出值的关键字。

  • 协程 —— 生成器通过 send 等机制扩展后即可作为协程使用。

  • 惰性求值 —— 生成器只在需要时才计算下一个值的特性。

  • 列表推导式 —— 与生成器表达式形式相近但会一次性构造完整列表。

  • 异步编程 —— 生成器曾用于组织异步控制流,后由 async 与 await 模型接替。

参考资料

  1. [科普中国]-生成器 [引用日期2026-10-14]
  2. function* [引用日期2026-10-14]
  3. PEP 255 – 简单的生成器 | peps.python.org [引用日期2026-10-14]
  4. ES6 Generator 函数 [引用日期2026-10-14]
  5. CLU [引用日期2026-10-14]
  6. yield [引用日期2026-10-14]
  7. functional [引用日期2026-10-14]
  8. 问为什么生成器表达式只能迭代一次?EN [引用日期2026-10-14]
  9. PEP 342 – Coroutines via Enhanced Generators [引用日期2026-10-14]
  10. expressions [引用日期2026-10-14]
  11. 生成器 - 精细的图形部分 | Blog | Chrome for Developers [引用日期2026-10-14]
  12. JavaScript生成器函数详解:从入门到实践指南 [引用日期2026-10-14]
  13. Python迭代器与生成器概览 [引用日期2026-10-14]
  14. 【ES6基础】生成器(Generator) [引用日期2026-10-14]
  15. Python 2.5 有什麼新功能¶ [引用日期2026-10-14]
词条评价
词条统计

浏览次数:4 次

阅读量:0 次 · 阅读完成量:0 次

最近更新:2026-10-13T21:47:01Z

历史版本

本条目引用的词条
迭代器 yield 协程 惰性求值 列表推导式 异步编程