Python 生成器表达式:省内存又优雅的写法

191 阅读4分钟

写 Python 的时候,你可能听过“生成器表达式”这个词,它看起来有点像列表推导式,但本质不太一样。本文就来带你从头到尾搞懂生成器表达式:它是什么、怎么用、有什么优势,还有常见的使用场景。

参考文章: Python 迭代器 | 简单一点学习 easyeasy.me

目录

  1. 什么是生成器表达式?
  2. 和列表推导式有什么区别?
  3. 生成器表达式的基本语法
  4. 如何从生成器表达式中取值?
  5. for 循环 + 生成器表达式
  6. 与内置函数组合使用(sum / max / any / all 等)
  7. 用生成器表达式节省内存
  8. 多层嵌套生成器表达式
  9. 生成器表达式的注意事项
  10. 小结与建议

1. 什么是生成器表达式?

生成器表达式(Generator Expression)是 Python 提供的一种快速创建生成器的方式,语法很像列表推导式,但不会一次性生成所有数据,而是每次只生成一个值

通俗讲:

它就像是个“懒人版”的列表,只在需要的时候给你值,节省内存。


2. 和列表推导式有什么区别?

来看一个例子直观感受下:

# 列表推导式:马上生成完整列表
squares_list = [x * x for x in range(5)]

# 生成器表达式:不会立刻计算,返回一个生成器对象
squares_gen = (x * x for x in range(5))

输出:

print(squares_list)  # [0, 1, 4, 9, 16]
print(squares_gen)   # <generator object ...>

✅ 列表推导式:快但占内存
✅ 生成器表达式:省内存但“懒惰”


3. 生成器表达式的基本语法

语法几乎和列表推导式一样,只不过把 [] 换成了 ()

(expression for item in iterable if condition)

例子:

gen = (x * 2 for x in range(5) if x % 2 == 0)
print(next(gen))  # 0
print(next(gen))  # 4
print(next(gen))  # 8

4. 如何从生成器表达式中取值?

生成器表达式的值是“一个个生成”的,可以用以下几种方式获取:

方法1:next()

g = (i for i in range(3))
print(next(g))  # 0
print(next(g))  # 1
print(next(g))  # 2
# print(next(g))  # StopIteration 错误

方法2:for 循环

for i in (x * 3 for x in range(4)):
    print(i)

输出:

0
3
6
9

5. for 循环 + 生成器表达式

你可以直接把生成器表达式嵌在 for 循环里:

for name in (s.strip() for s in [" Alice ", " Bob ", "Charlie "]):
    print(name)

这样写代码简洁又高效。


6. 与内置函数组合使用(sum / max / any / all 等)

生成器表达式非常适合跟一些内置函数组合使用,避免中间生成临时列表:

# 计算总和
total = sum(x for x in range(1000000))

# 找最大值
m = max(x for x in [3, 7, 2, 9])

# 判断是否有偶数
has_even = any(x % 2 == 0 for x in range(10))

# 判断是否所有数字都小于 100
all_small = all(x < 100 for x in range(50))

这样写,比先用列表推导式再传进去更节省资源。


7. 用生成器表达式节省内存

列表推导式在数据量很大时可能会炸内存,比如:

# 列表推导式会先生成一个完整列表,占用大量内存
big_list = [x for x in range(10**8)]

改成生成器表达式就安全多了:

big_gen = (x for x in range(10**8))

它不会一次性占内存,只会在你用 next()for 的时候才生成下一个值。


8. 多层嵌套生成器表达式

如果你想搞点复杂的嵌套,也没问题:

g = ((x, y) for x in range(3) for y in range(2))
for pair in g:
    print(pair)

输出:

(0, 0)
(0, 1)
(1, 0)
(1, 1)
(2, 0)
(2, 1)

注意:虽然能写,但太复杂时可读性不太好,适可而止哈。


9. 生成器表达式的注意事项

  • 只能遍历一次:生成器用过就没了,不能像列表那样重复用
  • 不能直接索引:不能用 gen[0],因为它不是列表
  • 不能回头:一旦走过了某个值,不能退回去,只能重建一个新的生成器

例子:

g = (x for x in range(3))
list(g)       # [0, 1, 2]
list(g)       # [],因为前面已经用完了

10. 小结与建议

特性列表推导式生成器表达式
返回类型listgenerator
内存使用高(一次性全部生成)低(按需生成)
速度慢一点(因为惰性)
可迭代次数无限次一次(用完即焚)
支持索引访问

建议使用时机:

  • 如果你只需要一次遍历、数据量很大、对速度要求不高 —— 用生成器表达式
  • 如果你要多次用这个结果、要随机访问 —— 用列表推导式