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的执行流程

  1. 调用生成器函数返回生成器对象
  2. next(gen)开始执行,到第一个yield暂停
  3. 返回yield的值
  4. 下次next()从暂停处继续
  5. 函数结束时抛出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速度、一次遍历

生成器是处理大数据的利器,掌握它能让你的程序更高效、更优雅。


练习题

  1. 写一个生成器,生成质数序列
  2. 用生成器实现文件的逐行读取和处理
  3. 组合多个生成器创建数据处理管道

本文代码示例

关注公众号:极客老墨

更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。

极客老墨微信公众号二维码

相关阅读