Ptyhon中的生成器(Generator)
Python 中的 生成器 (Generator) 是一种特殊的迭代器(Iterator)。
简单来说,生成器允许你一边循环一边计算,而不是一次性把所有结果计算出来存入内存。这使得它在处理大量数据或无限数据流时非常高效。
以下是关于生成器的详细解析:
1. 核心概念:为什么要用生成器?
假设你要处理 1 亿个数字。
- 列表 (List):会一次性创建 1 亿个数字并存入内存,占用极大内存,甚至导致程序崩溃。
- 生成器 (Generator):它不会一次性生成所有数字,而是保存算法。每次你需要一个数字时,它才计算一个给你。几乎不占内存。
这种机制被称为 惰性求值 (Lazy Evaluation)。
2. 如何创建生成器
有两种主要方式来创建生成器:
方法一:生成器表达式 (Generator Expression)
只要把列表推导式(List Comprehension)的 [] 换成 (),就变成了生成器。
python
# 列表推导式:立即生成所有数据
my_list = [x * x for x in range(5)]
print(my_list) # 输出: [0, 1, 4, 9, 16]
# 生成器表达式:不立即生成
my_generator = (x * x for x in range(5))
print(my_generator) # 输出: <generator object ... at 0x...>
# 需要通过循环或 next() 来取值
for num in my_generator:
print(num)
方法二:生成器函数 (使用 yield 关键字)
如果一个函数中包含了 yield 关键字,那么这个函数就不再是一个普通函数,而是一个生成器函数。
yield 和 return 的区别:
return:返回结果并结束函数。yield:返回结果并暂停函数,保留当前状态,等待下一次调用继续执行。
python
def simple_generator():
print("Step 1")
yield 1
print("Step 2")
yield 2
print("Step 3")
yield 3
# 创建生成器对象
gen = simple_generator()
# 执行
print(next(gen)) # 输出 "Step 1" 然后返回 1
print(next(gen)) # 输出 "Step 2" 然后返回 2
print(next(gen)) # 输出 "Step 3" 然后返回 3
# print(next(gen)) # 再次调用会抛出 StopIteration 异常
3. 实战案例:斐波那契数列
这是生成器最经典的用法。如果要生成前 N 个斐波那契数,使用列表可能会消耗大量内存,而生成器则非常轻量。
python
def fib(max_count):
n, a, b = 0, 0, 1
while n < max_count:
yield b # 返回当前的 b,并暂停在这里
a, b = b, a + b
n = n + 1
return 'done'
# 使用 for 循环遍历生成器
for x in fib(6):
print(x)
4. 生成器 vs 列表:内存对比
让我们看看处理大数据时两者的区别:
python
import sys
# 创建一个包含 100万 个数字的列表
list_data = [x for x in range(1000000)]
print(f"列表占用内存: {sys.getsizeof(list_data)} 字节")
# 创建一个包含 100万 个数字的生成器
gen_data = (x for x in range(1000000))
print(f"生成器占用内存: {sys.getsizeof(gen_data)} 字节")
典型输出结果:
- 列表占用内存: ~8,448,728 字节 (约 8MB)
- 生成器占用内存: ~112 字节 (极小,无论数据量多大,大小基本不变)
5. 进阶用法
next() 函数
生成器可以通过 next(gen) 来手动获取下一个值。当没有更多元素时,会抛出 StopIteration 异常。for 循环会自动捕获这个异常并停止循环。
yield from (Python 3.3+)
用于在一个生成器中调用另一个生成器(委托生成器)。常用于扁平化嵌套列表。
python
def sub_gen():
yield 'A'
yield 'B'
def main_gen():
yield 1
yield from sub_gen() # 相当于把 sub_gen 的值直接拿过来
yield 2
for item in main_gen():
print(item, end=' ')
# 输出: 1 A B 2
send() 方法
生成器不仅可以产出数据,还可以接收外部传进来的数据。
python
def interact():
print("开始")
val = yield 1 # 暂停,返回 1。下次恢复时,val 接收 send 发来的值
print(f"接收到了: {val}")
yield 2
g = interact()
print(next(g)) # 启动生成器,运行到 yield 1,输出 1
print(g.send("Hello")) # 发送 "Hello" 给 val,继续运行到 yield 2,输出 2
6. 总结
| 特性 | 列表 (List) | 生成器 (Generator) |
|---|---|---|
| 创建方式 | [] 或普通函数 |
() 或带 yield 的函数 |
| 内存占用 | 高 (存所有数据) | 低 (只存状态和算法) |
| 计算方式 | 立即计算所有结果 | 惰性计算 (用一个算一个) |
| 访问方式 | 索引访问 list[0] |
只能顺序迭代 (next 或 for) |
| 适用场景 | 数据量小,需要频繁随机访问 | 数据量大,无限数据流,只需遍历一次 |
一句话总结: 如果你需要遍历一个很大的数据集,或者数据是流式的(无限的),请优先使用生成器。
右滑查看面试常问