问题: Python 中推导式和生成器表达式的核心区别是什么?

👇 点击展开/折叠 面试背诵模板(建议先背这个) 推导式是立即执行并返回完整容器(列表/字典/集合)的语法,生成器表达式返回生成器对象,惰性求值按需产出数据。推导式用方括号 `[]` 或花括号 `{}`,生成器用圆括号 `()`。生成器迭代一次即耗尽,且不支持索引和 `len()`,但内存占用极小;推导式适合数据量可控的中等规模场景,生成器表达式适合处理海量数据流或无限序列。
---

语法形态与返回类型速览

写法 返回类型 执行时机 内存行为
[x**2 for x in range(10)] 列表(list) 立即计算 一次性分配全部元素
{x: x**2 for x in range(10)} 字典(dict) 立即计算 一次性分配全部键值对
{x%3 for x in range(10)} 集合(set) 立即计算 一次性分配全部元素
(x**2 for x in range(10)) 生成器(generator) 惰性(迭代时计算) 仅存当前状态,极省内存

执行时机与内存占用对比

1
2
3
4
5
6
7
8
9
import sys

# 列表推导式:立即申请内存存放100万个平方数
lst = [i**2 for i in range(1_000_000)]
print(sys.getsizeof(lst)) # 约 8MB(具体取决于系统)

# 生成器表达式:仅存储生成器对象本身,不生成数据
gen = (i**2 for i in range(1_000_000))
print(sys.getsizeof(gen)) # 约 112 字节(几乎无内存增长)

迭代特性与访问限制

生成器是一次性迭代器,遍历完后无法重置或重复使用,且不支持通过索引取值(gen[0] 报错),只能通过 next() 或 for 循环顺序访问。推导式返回的容器支持任意次遍历、索引切片和 len() 查询。

1
2
3
4
5
6
7
gen = (x for x in range(3))
print(list(gen)) # [0, 1, 2]
print(list(gen)) # [](已耗尽,第二次为空)

lst = [x for x in range(3)]
print(lst[1]) # 1(支持索引)
print(lst) # [0, 1, 2](可重复使用)

性能权衡与选型速查

场景 推荐方案 理由
数据量小(< 万级)且需多次遍历 列表推导式 速度更快,重复访问友好
数据量极大(百万级、GB级文件流) 生成器表达式 内存不爆炸,按需逐条处理
网络请求分页抓取(逐页返回) 生成器表达式 无需等待全部数据到达
需随机访问或修改已有元素 列表推导式 生成器不支持索引和赋值
链式调用(如 sum()、any()) 生成器表达式 函数内部遍历即可,省去中间容器

常见陷阱与最佳实践

  • 生成器不保留数据:若后续代码需要多次使用同一批数据,应显式转为列表或重新创建生成器,否则第二次迭代为空。
  • 嵌套生成器需逐层驱动:生成器内部包含生成器时,若不迭代内层,外层也不会计算(惰性会传递)。
  • 生成器内存优势随操作变化:sum((x for x in range(N))) 与 sum([x for x in range(N)]) 相比,前者不创建大列表,内存效率显著提升。