生成器通过惰性求值避免内存预分配,仅在next()调用时计算并交出一个值,内存占用远低于列表(如百万整数列表占28mb,生成器仅约120字节)。

直接用生成器替代返回列表,核心就一条:避免一次性把所有数据塞进内存。你写 return [x for x in data] 的那一刻,Python 就开始分配连续内存块、构造每个对象、维护引用计数——哪怕你后续只取前 3 个元素,它也照单全收。而生成器不预分配、不缓存、不打包,只在 next() 调用时算一个、交一个、忘一个。
生成器对象本身几乎不占内存
一个包含百万级整数的列表,在 CPython 中实际占用约 28MB(每个 int 对象平均 28 字节);而对应的生成器对象,sys.getsizeof(gen) 返回通常不到 120 字节。这不是“省一点”,是量级差两个数量级。
-
gen = (x * 2 for x in range(10**6))—— 创建瞬间完成,内存无波动 -
lst = [x * 2 for x in range(10**6)]—— 立即触发内存分配,可能触发 GC 或 OOM - 生成器对象内部只保存执行帧(
frame)、指令指针(f_lasti)和状态标记(gi_suspended),没有数据容器
yield 语句不是 return 的变体,而是协作式暂停点
很多人误以为 yield 是“能多次 return”,其实它根本不是返回值,而是让出控制权并冻结当前栈帧。下次调用 next() 时,解释器直接跳回 yield 下一行继续执行,变量值、循环状态、上下文全都在。
- 函数体里写
yield x,等于告诉解释器:“现在暂停,把 x 交出去,等下次要的时候我接着这儿干” - 没有
yield的函数,哪怕带return,也做不到状态保持;有yield就自动获得__iter__和__next__,符合迭代器协议 - 错误地在生成器里混用
return value(非空返回)会引发SyntaxError: 'return' with argument inside generator
生成器表达式比生成器函数更轻量,但灵活性受限
当你只需要简单映射或过滤,用括号包起来的生成器表达式((x for x in data if condition))比定义函数更快启动、更少开销;但一旦逻辑变复杂(比如需要状态变量、异常处理、嵌套循环),就得切回生成器函数。
-
sum(x * x for x in range(10**7))—— 安全,内存恒定在 KB 级 -
sum([x * x for x in range(10**7)])—— 极大概率 OOM,且多一次遍历构造列表 - 生成器表达式不能加文档字符串、不能被调试器断点捕获、不能重用(只能迭代一次)
真正容易被忽略的点是:生成器不是“慢一点但省内存”的折中方案,它是对“数据流”这一本质的诚实建模。你处理日志、读大文件、接实时 API 流,本来就不是在操作“一个完整的集合”,而是在响应“下一个事件”。硬塞成列表,等于先造一座桥再拆掉——既费料,又耽误过河。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











