Python 生成器是如何节省内存的

7次阅读

生成器函数返回迭代器对象而非列表,调用时立即返回generator,每次yield挂起并保存状态,仅按需计算单个值,内存占用极小;生成器表达式同理,但不可索引、不可重复遍历。

Python 生成器是如何节省内存的

生成器函数返回的是迭代器对象,不是一次性列表

调用 def 定义的生成器函数(含 yield)时,python 不执行函数体,而是立即返回一个 generator 对象。这个对象是迭代器,支持 __next__(),但内部不保存全部结果。

对比普通函数:

  • 返回 list 的函数:必须在内存中构造完整列表,比如 [x**2 for x in range(1000000)] 会立刻占用数百 MB
  • 生成器函数:每次只计算并产出一个值,例如 def squares(n): yield from (i**2 for i in range(n)),调用时几乎不占额外内存

yield 让函数挂起并保留局部状态

每次遇到 yield,函数暂停执行,把值交还给调用方,同时冻结当前帧(包括局部变量、指令指针等)。下次调用 __next__() 时从中断处继续,而不是重新开始。

这意味着:

立即学习Python免费学习笔记(深入)”;

  • 不需要为整个序列分配连续内存空间
  • 递归或复杂状态(如文件读取位置、计数器)可自然保留在生成器内部
  • 若生成器未被完全消费(比如只取前 5 个值),后续逻辑不会触发,对应计算被跳过

生成器表达式比列表推导式更省内存

(x**2 for x in range(10**6)) 是生成器表达式,而 [x**2 for x in range(10**6)] 是列表推导式。前者本质是匿名生成器函数调用,后者直接构建对象列表。

关键差异:

  • 生成器表达式不支持索引、len()、重复遍历;必须用 for 或显式 next()
  • 若需多次使用结果,生成器只能消费一次,强行转成 list() 就失去内存优势
  • 嵌套生成器(如 (y for x in data for y in x))仍保持单次流式处理,无中间容器

注意生成器无法回退和随机访问

生成器是单向迭代器,没有 seek()reset() 方法。一旦调用 next() 走过某个值,它就从内存中“消失”了(除非你额外缓存)。

容易踩的坑:

  • 误以为 list(gen) 后还能继续用 gen —— 实际上已耗尽,再调用 next(gen) 会抛 StopIteration
  • for 循环中意外中断(如 break 或异常),可能导致资源未清理(建议用 try/finally 或上下文管理器封装
  • 调试时打印 gen 只看到 ,看不到内容,得用 list(gen) 或逐个 next() 查看

真正省内存的前提,是你别把它一次性全展开。只要保持“按需产出、即产即用”的节奏,生成器的轻量级状态机机制才能发挥效果。

text=ZqhQzanResources