生成器函数并不是为“节省内存”这一单一目标而设计的。它真正提供的是一种分阶段执行模型:函数可以在多个 yield 之间暂停与恢复,其执行状态由运行期对象承载。

因此,生成器函数适用于一类具有共同特征的场景:

• 结果需要逐步产生

• 过程需要阶段性推进

• 状态应自然嵌入控制流

一、构造惰性序列

生成器函数最直接的用途,是构造“惰性序列”。与一次性构造完整数据结构不同,生成器在每次请求元素时才推进到下一个 yield。

示例:

        

使用:

print(list(g))

输出:

[4, 9, 16]

可以看到:

• 生成器不会预先生成全部结果

• 每次 next() 仅推进一次执行

• 函数体在 yield 处暂停

• 局部变量与指令位置被保留

这使生成器特别适合:

• 数据规模较大

• 只消费部分结果

• 计算代价较高

生成器不仅仅是列表推导式的惰性版本,而是一种“可暂停函数”的执行模型。

二、表达无限序列

生成器函数天然适合表达理论上无界的数据流。由于执行是按需推进的,无需预先构造完整结构。

示例

        

使用:

输出:

10 11 12 13 14

该生成器在逻辑上是无限的,但由于执行按需推进,程序仍可稳定运行。

这种能力在以下场景中尤为重要:

• 实时数据流

• 持续监听事件

• 模拟连续信号

生成器将“潜在无限”转化为“有限推进”。

三、流式数据处理

生成器函数常被用于封装 I/O 逻辑,使数据处理呈现为连续流。

示例:逐行读取大文件

            

调用方式:

    

这种结构具有明显优势:

• 不一次性加载全部数据

• 文件资源在生成器执行完成后自动释放

调用方无需关心 I/O 管理细节

生成器在这里提供的是一种“按需交付”的结构能力。

四、构建数据处理管道

生成器函数可以相互组合,形成分层数据处理管道。每一层负责单一转换逻辑。

例如:

        

组合使用:

输出:

0 4 16 36 64

该结构的特征:

• 数据逐层流动

• 每层只处理当前元素

• 无需构造中间列表

• 保持惰性执行

生成器函数在此成为构建数据流管道的核心工具。

五、表达状态机与多阶段流程

生成器函数可以自然表达多阶段执行逻辑。

示例:简单有限状态机

        

阶段顺序由代码结构表达,而无需显式维护状态变量。

再如分阶段任务:

    

控制流程:

next(t)

生成器将“阶段位置”嵌入控制流结构,而不是通过布尔变量或枚举值管理。

六、替代临时中间结构

生成器表达式常用于替代临时列表,从而避免额外内存占用。从语义层面看,它等价于一个匿名生成器函数的即时调用。

比如像 sum() 这样的会逐步消费生成器,不会构造中间完整列表:

total = sum(x * x for x in range(1000000))

若写成:

total = sum([x * x for x in range(1000000)])

则会构造一个包含一百万个元素的列表,占用额外内存。

生成器表达式将计算与消费合并为惰性流程,是性能优化与结构清晰之间的合理折中。

七、协程式交互

生成器函数不仅能产出值,还能通过 send() 接收外部输入。

示例:

        

使用:

e.send("world")

在这里,yield 既是暂停点,同时也是输入接收点。

控制权在调用方与生成器之间显式交替。这种机制在 async/await 引入之前,已被用于实现协程框架。

小结

生成器是以函数语法构造、以迭代语义运行的执行结构。它通过 yield 将连续执行拆分为可暂停、可恢复的推进过程。惰性序列构造、无限数据流表达、流式处理、管道组合以及状态机实现,均依赖于这一分阶段执行模型。生成器将“函数调用”转化为“可推进执行过程”,在对象模型层面提供了一种精细而高效的控制机制。

打开网易新闻 查看精彩图片

点赞有美意,赞赏是鼓励