Logo
Published on

3.5.生成器

Authors
  • avatar
    Name
    xiaobai
    Twitter

1.基本概念

生成器是 Python 中一种特殊的迭代器,使用 yield 关键字创建,具有惰性计算特性。

1.1.什么是生成器?

生成器是一种特殊的函数,具有以下特征:

  • 惰性计算:按需生成值,不立即计算所有结果
  • 内存高效:一次只处理一个值,不存储整个序列
  • 可迭代:可以用在 for 循环中
  • 状态保持:记住上次执行的位置
  • 协程特性:支持双向通信,可以接收外部数据

2.创建生成器的方法

生成器有两种主要创建方式:

  1. 生成器函数:使用 yield 关键字的函数
  2. 生成器表达式:类似列表推导式的语法,但使用圆括号

2.1.生成器函数 vs 普通函数

特性普通函数生成器函数
返回值使用 return使用 yield
执行方式一次性执行完毕可以暂停和恢复
内存使用一次性创建所有结果按需生成,节省内存
状态保持不保持状态保持执行状态

优势

  • 内存效率高,适合处理大数据集
  • 支持复杂的控制流
  • 可以实现协程模式
  • 代码更简洁易读

2.2.方法1:生成器函数

生成器函数使用 yield 关键字创建,每次遇到 yield 会暂停执行并返回值。

语法特点

  • 使用 def 关键字定义
  • 函数体内必须包含 yield 语句
  • 调用时返回生成器对象,不立即执行
  • 通过 next()for 循环驱动执行
def my_generator():
    """简单的生成器函数示例"""
    print("第一步")
    yield 'A'
    print("第二步")
    yield 'B'
    print("第三步")
    yield 'C'

# 创建生成器对象
gen = my_generator()

# 逐步获取值
print(next(gen))  # 输出: 第一步 \n A
print(next(gen))  # 输出: 第二步 \n B
print(next(gen))  # 输出: 第三步 \n C
# print(next(gen))  # 抛出 StopIteration 异常

# 使用 for 循环遍历
for value in my_generator():
    print(f"获取到: {value}")

重要特性

  • 生成器只能遍历一次,用完即耗尽
  • 每次 yield 会保存函数状态
  • 适合处理大量数据或复杂逻辑
  • 支持协程模式(双向通信)

2.3.方法2:生成器表达式

生成器表达式是创建生成器最简洁的方式,语法类似列表推导式,但使用圆括号。

基本语法

(expression for item in iterable [if condition])

示例

# 创建平方数生成器
gen = (x**2 for x in range(5))

print(type(gen))  # <class 'generator'>
print(next(gen))  # 0
print(next(gen))  # 1
print(next(gen))  # 4
print(next(gen))  # 9
print(next(gen))  # 16
# print(next(gen))  # StopIteration

# 使用 for 循环遍历
for value in (x*3 for x in range(4)):
    print(value, end=" ")  # 输出: 0 3 6 9

# 带条件的生成器表达式
even_squares = (x**2 for x in range(10) if x % 2 == 0)
print(list(even_squares))  # [0, 4, 16, 36, 64]

优势

  • 语法简洁,一行代码创建生成器
  • 内存效率高,惰性求值
  • 适合简单的数据转换和过滤
  • 可以与其他函数链式组合

适用场景

  • 简单的数据转换
  • 一次性遍历
  • 内存敏感的场景
  • 与内置函数配合使用

2.4.列表推导式 vs 生成器表达式

numbers = [1, 2, 3, 4, 5]

# 列表推导式 - 立即计算所有结果
squares_list = [x**2 for x in numbers]
print(squares_list)  # [1, 4, 9, 16, 25]
print(type(squares_list))  # <class 'list'>

# 生成器表达式 - 惰性计算
squares_gen = (x**2 for x in numbers)
print(squares_gen)  # <generator object <genexpr> at 0x...>
print(type(squares_gen))  # <class 'generator'>

# 遍历生成器
for square in squares_gen:
    print(square, end=" ")  # 1 4 9 16 25

对比总结

特性列表推导式生成器表达式
语法[expr for item in iterable](expr for item in iterable)
内存使用一次性创建所有元素按需生成,节省内存
执行时机立即执行惰性求值
可重复使用否(只能遍历一次)
适用场景需要多次访问结果一次性遍历,大数据集

3.生成器的工作原理

3.1.执行流程

生成器本质上是一个特殊的迭代器,其执行流程与普通函数不同:

  1. 创建阶段:调用生成器函数时,函数体不会立即执行,而是返回生成器对象
  2. 驱动阶段:通过 next()for 循环驱动执行
  3. 暂停恢复:遇到 yield 暂停并返回值,下次从暂停处继续
  4. 结束阶段:函数执行完毕时抛出 StopIteration 异常
def example_gen():
    print("Step 1")
    yield 'A'
    print("Step 2")
    yield 'B'
    print("End")

gen = example_gen()
print(next(gen))  # 输出: Step 1 \n A
print(next(gen))  # 输出: Step 2 \n B
print(next(gen))  # 输出: End \n 抛出 StopIteration

执行步骤

  • 创建生成器:仅返回对象,不执行函数体
  • 第一次 next():从函数开始执行,遇到第一个 yield 暂停
  • 后续 next():从上次暂停处恢复,直到函数结束

状态保持

  • 生成器能记住执行位置和局部变量状态
  • 每次 yield 会保存当前执行状态
  • 下次调用时从保存的状态继续执行

3.2.状态保持示例

def fibonacci_generator():
    """生成斐波那契数列的生成器函数"""
    a, b = 0, 1  # 初始化前两个斐波那契数
    while True:  # 无限循环
        yield a  # 返回当前的a值
        a, b = b, a + b  # 计算下一个斐波那契数

# 创建生成器
fib = fibonacci_generator()

# 获取前10个斐波那契数
for i in range(10):
    print(next(fib), end=" ")  # 输出: 0 1 1 2 3 5 8 13 21 34

状态保持特点

  • 变量 ab 的值在每次 yield 后保持
  • 下次调用时从上次暂停的位置继续
  • 不需要额外的数据结构来保存状态
  • 适合生成无限序列

4.生成器方法

生成器提供了几个特殊方法,支持更高级的交互和控制。

4.1.send() 方法 - 向生成器发送值

send() 方法可以向生成器发送值,实现双向通信。

使用规则

  • 首次启动生成器必须使用 next()
  • 只有生成器暂停在 yield 后才能使用 send()
  • send() 会返回生成器产生的下一个值
def simple_echo():
    """简单的回声生成器"""
    received = yield "请给我一个值"
    yield f"你发来的是:{received}"

gen = simple_echo()
print(next(gen))           # 输出: 请给我一个值
print(gen.send("Hello"))   # 输出: 你发来的是:Hello

执行流程

  1. next(gen) 启动生成器,执行到第一个 yield
  2. gen.send("Hello") 将值发送给生成器,继续执行
  3. 生成器接收值并处理,产生下一个结果

应用场景

  • 协程编程
  • 状态机实现
  • 数据管道处理
  • 与外部系统交互

4.2.throw() 方法 - 向生成器抛出异常

throw() 方法可以在生成器暂停处抛出异常,实现异常处理机制。

使用场景

  • 外部主动通知生成器发生错误
  • 优雅地中断生成器执行
  • 实现异常处理和恢复逻辑
def exception_generator():
    """演示异常处理的生成器"""
    try:
        yield "开始"
        yield "继续"
        yield "结束"
    except ValueError as e:
        yield f"捕获异常: {e}"
        yield "恢复执行"

# 使用示例
gen = exception_generator()
print(next(gen))        # 开始
print(next(gen))        # 继续
print(gen.throw(ValueError("测试异常")))  # 捕获异常: 测试异常
print(next(gen))        # 恢复执行

异常处理流程

  1. 生成器正常执行到 yield 语句
  2. 外部调用 throw() 抛出异常
  3. 生成器内部捕获异常并处理
  4. 可以选择恢复执行或终止生成器

注意事项

  • 如果异常未被捕获,会向外传播
  • 异常处理完成后,生成器可以继续执行
  • 适合实现错误恢复和资源清理

4.3.close() 方法 - 关闭生成器

close() 方法用于主动终止生成器,触发资源清理。

使用场景

  • 读取大文件时提前终止
  • 数据库连接管理
  • 网络连接清理
  • 资源释放
def closable_generator():
    """可关闭的生成器示例"""
    try:
        yield "第一步"
        yield "第二步"
        yield "第三步"
    except GeneratorExit:
        print("生成器被关闭,正在清理资源")
        raise  # 必须重新抛出异常

# 使用示例
gen = closable_generator()
print(next(gen))  # 第一步
gen.close()       # 生成器被关闭,正在清理资源
# 后续调用 next(gen) 会抛出 StopIteration

关闭流程

  1. 调用 close() 方法
  2. 生成器在 yield 处抛出 GeneratorExit 异常
  3. 生成器内部捕获异常并清理资源
  4. 必须重新抛出异常,否则会触发 RuntimeError

注意事项

  • 只能在生成器暂停时关闭
  • 关闭后再次调用 next() 会抛出 StopIteration
  • 适合实现资源管理和清理逻辑

5.实际应用场景

5.1.处理大文件

生成器非常适合处理大文件,避免内存溢出:

def read_large_file(file_path):
    """逐行读取大文件,避免内存溢出"""
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            yield line.strip()

def process_log_file(file_path):
    """处理日志文件,只包含错误日志"""
    for line in read_large_file(file_path):
        if 'ERROR' in line:
            yield line

# 使用示例
for error_line in process_log_file('huge_log_file.log'):
    print(error_line)

优势

  • 内存效率高,不会一次性加载整个文件
  • 支持流式处理,边读边处理
  • 可以随时停止处理
  • 适合处理GB级别的文件

5.2.数据管道

生成器可以构建高效的数据处理管道:

def data_processing_pipeline(data):
    """数据处理管道"""
    # 步骤1: 过滤有效数据
    valid_data = (item for item in data if item is not None)
    
    # 步骤2: 转换数据
    transformed_data = (str(item).upper() for item in valid_data)
    
    # 步骤3: 添加序号
    numbered_data = (f"{i}: {item}" for i, item in enumerate(transformed_data, 1))
    
    return numbered_data

# 使用管道
raw_data = [None, 'hello', None, 'world', 'python', None]
result = data_processing_pipeline(raw_data)

for item in result:
    print(item)
# 输出:
# 1: HELLO
# 2: WORLD
# 3: PYTHON

管道优势

  • 每个步骤都是惰性计算
  • 内存效率高,不存储中间结果
  • 易于组合和扩展
  • 支持流式处理

5.3.无限序列

生成器可以创建无限序列,按需生成数据:

def prime_generator():
    """生成质数的无限序列"""
    primes = []
    num = 2
    while True:
        is_prime = True
        # 用已找到的质数检查 num
        for prime in primes:
            if prime * prime > num:
                break
            if num % prime == 0:
                is_prime = False
                break
        if is_prime:
            primes.append(num)
            yield num
        num += 1

# 获取前20个质数
prime_gen = prime_generator()
first_20_primes = [next(prime_gen) for _ in range(20)]
print(first_20_primes)  # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]

无限序列优势

  • 按需生成,不预计算所有值
  • 内存效率高,只保存必要状态
  • 可以随时停止生成
  • 适合数学计算和模拟

6.高级生成器模式

6.1.生成器委托 (yield from)

yield from 语句可以简化生成器委托,让代码更简洁。

基本语法

yield from iterable

对比示例

# 原始写法
def chain_generators_old(*iterables):
    for iterable in iterables:
        for item in iterable:
            yield item

# 使用 yield from
def chain_generators(*iterables):
    for iterable in iterables:
        yield from iterable

# 使用示例
gen1 = (x for x in range(3))
gen2 = (x for x in range(3, 6))
gen3 = (x for x in range(6, 9))

chained = chain_generators(gen1, gen2, gen3)
print(list(chained))  # [0, 1, 2, 3, 4, 5, 6, 7, 8]

优势

  • 代码更简洁,无需嵌套循环
  • 自动处理子生成器的返回值
  • 支持协程通信
  • 适合递归遍历树形结构

应用场景

  • 拼接多个生成器
  • 递归遍历树形结构
  • 协程通信
  • 管道式编程

6.2.协程模式

协程是生成器的重要扩展,支持双向通信和协作式编程。

协程特点

  • 使用 yield 暂停和恢复执行
  • 支持双向数据交换
  • 可以接收外部发送的数据
  • 适合异步任务调度
def average_coroutine():
    """协程模式:实时计算平均值"""
    total = 0
    count = 0
    average = 0
    
    while True:
        value = yield average  # 接收外部数据
        if value is None:
            break
        total += value
        count += 1
        average = total / count
    
    return average

# 使用协程
coro = average_coroutine()
next(coro)                # 预激协程
print(coro.send(10))      # 10.0
print(coro.send(20))      # 15.0
print(coro.send(30))      # 20.0

try:
    coro.send(None)       # 终止协程
except StopIteration as e:
    print('最终平均值:', e.value)  # 最终平均值: 20.0

协程优势

  • 支持双向通信
  • 可以暂停和恢复执行
  • 适合异步编程
  • 代码结构清晰

应用场景

  • 异步任务调度
  • 流式数据处理
  • 事件驱动编程
  • 状态机实现

7.生成器与迭代器

7.1.关系说明

生成器是 Python 中实现迭代器协议的一种简便方式:

  • 生成器本身就是特殊的迭代器,实现了 __iter__()__next__() 方法
  • 所有生成器都能用于 for 循环,与其他迭代器无缝兼容
  • 生成器提供了更简洁的迭代器实现方式

7.2.对比:迭代器类 vs 生成器函数

# 方法1: 自定义迭代器类
class SquareIterator:
    def __init__(self, n):
        self.n = n
        self.current = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current >= self.n:
            raise StopIteration
        result = self.current ** 2
        self.current += 1
        return result

# 方法2: 生成器函数
def square_generator(n):
    for i in range(n):
        yield i ** 2

# 使用对比
print("迭代器类:")
for x in SquareIterator(5):
    print(x, end=" ")  # 0 1 4 9 16

print("\n生成器函数:")
for x in square_generator(5):
    print(x, end=" ")  # 0 1 4 9 16

对比总结

特性迭代器类生成器函数
代码量较多较少
状态管理手动管理自动管理
异常处理手动抛出 StopIteration自动处理
可读性一般更好
适用场景复杂逻辑简单到中等复杂度

推荐使用生成器函数,除非需要复杂的迭代逻辑。

8.性能比较

8.1.内存使用对比

生成器在处理大数据时具有明显的内存优势:

import sys

# 列表推导式 - 一次性创建所有元素
def using_list(n):
    result = [i**2 for i in range(n)]
    return sum(result)

# 生成器表达式 - 按需生成元素
def using_generator(n):
    result = (i**2 for i in range(n))
    return sum(result)

n = 1000000

# 内存使用对比
list_result = using_list(n)
gen_result = using_generator(n)

print(f"列表内存使用: {sys.getsizeof(list_result)} 字节")
print(f"生成器内存使用: {sys.getsizeof(gen_result)} 字节")
# 输出示例:
# 列表内存使用: 800984 字节
# 生成器内存使用: 200 字节

8.2.性能优势总结

特性列表生成器
内存使用一次性创建所有元素按需生成,节省内存
执行速度需要预先计算惰性求值,边用边算
适用场景需要多次访问结果一次性遍历,大数据集
内存风险可能内存溢出内存安全

选择建议

  • 使用列表:需要多次访问结果,数据量不大
  • 使用生成器:一次性遍历,大数据集,内存敏感场景

9.实用示例

9.1.分块处理数据

生成器非常适合分块处理大型文件,避免内存溢出:

def chunk_reader(file_path, chunk_size=1024):
    """分块读取文件"""
    with open(file_path, 'rb') as file:
        while True:
            chunk = file.read(chunk_size)
            if not chunk:
                break
            yield chunk

def batch_processor(data, batch_size=100):
    """批量处理数据"""
    batch = []
    for item in data:
        batch.append(item)
        if len(batch) >= batch_size:
            yield batch
            batch = []
    if batch:  # 处理剩余数据
        yield batch

def process_large_dataset(data_generator):
    """处理大型数据集"""
    for batch in batch_processor(data_generator, batch_size=50):
        print(f"处理批次,大小: {len(batch)}")
        processed_batch = [item * 2 for item in batch]
        yield from processed_batch

# 使用示例
data_generator = chunk_reader("huge_log_file.log")
for item in process_large_dataset(data_generator):
    print(item)

分块处理优势

  • 内存效率高,不会一次性加载整个文件
  • 支持流式处理,边读边处理
  • 可以处理任意大小的文件
  • 易于扩展和组合

9.2.状态机生成器

生成器可以实现状态机,利用协程特性保持状态:

def state_machine():
    """使用生成器实现状态机"""
    state = "开始"
    while True:
        if state == "开始":
            command = yield "状态: 开始 - 输入 '运行' 或 '退出'"
            if command == "运行":
                state = "运行中"
            elif command == "退出":
                state = "结束"
        
        elif state == "运行中":
            command = yield "状态: 运行中 - 输入 '暂停' 或 '停止'"
            if command == "暂停":
                state = "暂停"
            elif command == "停止":
                state = "开始"
        
        elif state == "暂停":
            command = yield "状态: 暂停 - 输入 '继续' 或 '停止'"
            if command == "继续":
                state = "运行中"
            elif command == "停止":
                state = "开始"
        
        elif state == "结束":
            return "程序结束"

# 使用状态机
machine = state_machine()
print(next(machine))  # 启动
print(machine.send("运行"))
print(machine.send("暂停"))
print(machine.send("继续"))
print(machine.send("停止"))

状态机优势

  • 利用协程特性保持状态
  • 支持交互式控制
  • 代码结构清晰
  • 易于扩展和维护

应用场景

  • 游戏状态管理
  • 工作流控制
  • 用户交互系统
  • 协议状态机

10.最佳实践

10.1.使用建议

  1. 内存敏感时使用:处理大数据集时优先考虑生成器
  2. 一次性使用:生成器通常只能迭代一次
  3. 合理使用 yield from:简化生成器委托
  4. 及时关闭:不再使用的生成器应该关闭
  5. 异常处理:在生成器中适当处理异常
  6. 文档说明:说明生成器的行为和预期输入

10.2.性能优化

  • 优先使用生成器表达式处理简单逻辑
  • 使用生成器函数处理复杂逻辑
  • 避免在生成器中执行耗时操作
  • 合理使用 yield from 简化代码

11.常见陷阱

11.1.陷阱1: 生成器只能使用一次

def one_time_use():
    gen = (x for x in range(3))
    print(list(gen))  # [0, 1, 2]
    print(list(gen))  # [] - 空的!

解决方案:每次需要遍历时重新创建生成器

11.2.陷阱2: 生成器表达式语法

# 正确写法
gen1 = (x**2 for x in range(5))

# 函数调用时可以省略外层括号
result = sum(x**2 for x in range(5))

注意:生成器表达式必须用圆括号包围

11.3.陷阱3: 过早耗尽生成器

def premature_exhaustion():
    gen = (x for x in range(5))
    if 3 in gen:  # 这会消耗生成器直到找到3
        print("找到3")
    # 此时生成器已经耗尽
    for item in gen:
        print(item)  # 不会执行

解决方案:避免在检查后继续使用生成器,或重新创建

11.4.陷阱4: 生成器中的异常处理

def exception_handling():
    try:
        yield "开始"
        yield "继续"
        yield "结束"
    except Exception as e:
        yield f"错误: {e}"
        # 注意:异常处理后生成器可能无法继续

建议:在生成器中谨慎处理异常,确保状态一致性

img