Python教程22:生成器(Generator)
“按需而生,用时即弃。”
生成器是Python中一个强大但常被忽视的特性。它能让你处理超大数据集而不耗尽内存,今天我们深入学习生成器的原理和应用。
1. 什么是生成器
问题场景
假设要生成1亿个数字:
1# 方法1:列表(占用大量内存)
2numbers = [i for i in range(100000000)]
3# 内存占用:约800MB!
4
5# 方法2:生成器(几乎不占内存)
6numbers = (i for i in range(100000000))
7# 内存占用:几百字节
生成器(Generator):
- 一种特殊的迭代器
- 惰性求值:需要时才计算,不提前生成所有值
- 内存高效:一次只保存一个值
- 使用
yield关键字或生成器表达式创建
为什么需要生成器:
- 处理大数据集(GB/TB级别)
- 无限序列(斐波那契数列、素数序列)
- 管道式数据处理
- 节省内存
2. 生成器函数
使用yield
普通函数用return返回值,生成器函数用yield:
1def simple_generator():
2 """
3 生成器函数示例
4 - yield关键字使函数变成生成器
5 - 每次yield都会"暂停"函数,保存状态
6 - 下次调用时从暂停处继续
7 """
8 print("开始")
9 yield 1
10 print("继续")
11 yield 2
12 print("再继续")
13 yield 3
14 print("结束")
15
16# 创建生成器对象(还没执行函数体)
17gen = simple_generator()
18print(type(gen)) # <class 'generator'>
19
20# 每次调用next()执行到下一个yield
21print(next(gen)) # 输出:开始 1
22print(next(gen)) # 输出:继续 2
23print(next(gen)) # 输出:再继续 3
24# print(next(gen)) # StopIteration异常
yield的执行流程:
- 调用生成器函数返回生成器对象
next(gen)开始执行,到第一个yield暂停- 返回yield的值
- 下次
next()从暂停处继续 - 函数结束时抛出StopIteration
实际应用
1def countdown(n):
2 """倒计时生成器"""
3 print(f"从{n}开始倒数")
4 while n > 0:
5 yield n
6 n -= 1
7 print("发射!")
8
9for count in countdown(5):
10 print(count)
11# 输出:
12# 从5开始倒数
13# 5
14# 4
15# 3
16# 2
17# 1
18# 发射!
3. 生成器表达式
类似列表推导式,但用圆括号:
1# 列表推导式:立即生成所有值
2squares_list = [x**2 for x in range(10)]
3print(type(squares_list)) # <class 'list'>
4
5# 生成器表达式:惰性计算
6squares_gen = (x**2 for x in range(10))
7print(type(squares_gen)) # <class 'generator'>
8
9# 使用
10for sq in squares_gen:
11 print(sq)
对比:
| 特性 | 列表推导式 | 生成器表达式 |
|---|---|---|
| 语法 | [...] | (...) |
| 内存 | 所有元素 | 一个元素 |
| 速度 | 快(已生成) | 慢(按需生成) |
| 遍历 | 多次 | 一次 |
1# 大数据时的区别
2import sys
3
4list_comp = [i for i in range(100000)]
5gen_exp = (i for i in range(100000))
6
7print(sys.getsizeof(list_comp)) # 约800KB
8print(sys.getsizeof(gen_exp)) # 约128字节
4. 无限生成器
生成器可以生成无限序列:
1def fibonacci():
2 """
3 斐波那契数列生成器(无限)
4 - 不会预先计算所有值
5 - 可以随时停止
6 """
7 a, b = 0, 1
8 while True: # 无限循环
9 yield a
10 a, b = b, a + b
11
12# 使用
13fib = fibonacci()
14for i in range(10):
15 print(next(fib), end=" ")
16# 输出:0 1 1 2 3 5 8 13 21 34
1def counter(start=0):
2 """无限计数器"""
3 while True:
4 yield start
5 start += 1
6
7c = counter(100)
8print(next(c)) # 100
9print(next(c)) # 101
10print(next(c)) # 102
5. 生成器的高级特性
send()方法
生成器可以接收值:
1def echo_generator():
2 """
3 可以接收值的生成器
4 - send()向生成器发送值
5 - yield表达式可以接收这个值
6 """
7 while True:
8 # yield既返回值,也接收值
9 received = yield
10 print(f"收到:{received}")
11
12gen = echo_generator()
13next(gen) # 必须先启动生成器
14gen.send("Hello") # 输出:收到:Hello
15gen.send("World") # 输出:收到:World
生成器管道
多个生成器可以串联,形成数据处理管道:
1def read_large_file(file_path):
2 """
3 读取大文件的生成器
4 - 一次读一行,而不是整个文件
5 - 适合处理GB级别的文件
6 """
7 with open(file_path) as f:
8 for line in f:
9 yield line.strip()
10
11def filter_comments(lines):
12 """过滤注释行"""
13 for line in lines:
14 if not line.startswith('#'):
15 yield line
16
17def to_uppercase(lines):
18 """转大写"""
19 for line in lines:
20 yield line.upper()
21
22# 管道组合
23lines = read_large_file('data.txt')
24lines = filter_comments(lines)
25lines = to_uppercase(lines)
26
27# 惰性执行:只在遍历时才实际处理
28for line in lines:
29 print(line)
管道的优势:
- 内存效率:同时只处理一行
- 模块化:每个函数职责单一
- 可组合:像乐高一样拼接
6. 实用的生成器
itertools模块
Python标准库itertools提供了丰富的生成器工具:
1import itertools
2
3# itertools是Python标准库
4# 提供高效的迭代器工具,都是生成器
5
6# count:无限计数
7counter = itertools.count(10, 2) # 从10开始,步长2
8for i in itertools.islice(counter, 5): # 只取5个
9 print(i, end=" ") # 10 12 14 16 18
10
11# cycle:无限循环
12colors = itertools.cycle(['red', 'green', 'blue'])
13for i, color in enumerate(colors):
14 if i >= 5:
15 break
16 print(color, end=" ") # red green blue red green
17
18# chain:连接多个迭代器
19combined = itertools.chain([1, 2], [3, 4], [5, 6])
20print(list(combined)) # [1, 2, 3, 4, 5, 6]
自定义实用生成器
1def batch(iterable, n):
2 """
3 分批生成器:将迭代器分成固定大小的批次
4 - 处理大数据时分批加载
5 - 数据库批量插入
6 """
7 batch = []
8 for item in iterable:
9 batch.append(item)
10 if len(batch) == n:
11 yield batch
12 batch = []
13 if batch: # 剩余的
14 yield batch
15
16# 使用
17for group in batch(range(10), 3):
18 print(group)
19# [0, 1, 2]
20# [3, 4, 5]
21# [6, 7, 8]
22# [9]
7. 生成器vs普通函数
1# 普通函数
2def get_squares(n):
3 """返回平方列表"""
4 result = []
5 for i in range(n):
6 result.append(i ** 2)
7 return result # 一次返回所有
8
9# 生成器
10def gen_squares(n):
11 """生成平方数"""
12 for i in range(n):
13 yield i ** 2 # 一次返回一个
14
15# 比较
16squares_list = get_squares(1000000) # 占用大量内存
17squares_gen = gen_squares(1000000) # 几乎不占内存
18
19# 都可以遍历
20for sq in squares_list:
21 pass
22
23for sq in squares_gen:
24 pass
8. 注意事项
只能遍历一次
1gen = (x for x in range(5))
2print(list(gen)) # [0, 1, 2, 3, 4]
3print(list(gen)) # [](已耗尽)
4
5# 需要重新创建
6gen = (x for x in range(5))
7print(list(gen)) # [0, 1, 2, 3, 4]
不能用len()
1gen = (x for x in range(10))
2# len(gen) # TypeError: 生成器没有长度
调试困难
生成器的惰性特性让调试变困难:
1# 可以转换为列表查看
2gen = (x**2 for x in range(5))
3print(list(gen)) # 但会失去惰性优势
9. 小结
今天我们学习了生成器:
- 定义:惰性求值的迭代器
- 创建:
yield关键字、生成器表达式 - 优势:内存高效、支持无限序列
- 高级:send()、生成器管道
- itertools:标准库的生成器工具
- 权衡:内存vs速度、一次遍历
生成器是处理大数据的利器,掌握它能让你的程序更高效、更优雅。
练习题:
- 写一个生成器,生成质数序列
- 用生成器实现文件的逐行读取和处理
- 组合多个生成器创建数据处理管道
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
