Python 生成器与迭代器:惰性求值的强大力量

6 阅读5分钟

引言

在 Python 中,处理大量数据时,一次性将所有数据加载到内存中往往是不现实的。生成器(Generator)和迭代器(Iterator)提供了一种惰性求值(Lazy Evaluation)的机制,让你能够高效地处理海量数据,同时保持代码的简洁和优雅。

今天我们将深入探讨生成器和迭代器的工作原理、实用场景以及高级技巧。

迭代器协议

迭代器是实现了 __iter__()__next__() 方法的对象。每次调用 __next__() 返回下一个元素,当没有更多元素时抛出 StopIteration 异常。

class CountDown:
    """自定义迭代器:倒计时"""
    def __init__(self, start):
        self.current = start
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1

# 使用
for num in CountDown(5):
    print(num)  # 输出:5, 4, 3, 2, 1

生成器:更简洁的迭代器

生成器是特殊的迭代器,使用 yield 关键字定义。函数中包含 yield 时,它就变成了生成器函数,调用时返回生成器对象而非执行函数体。

基础生成器

def fibonacci(n):
    """生成前 n 个斐波那契数"""
    a, b = 0, 1
    count = 0
    while count < n:
        yield a
        a, b = b, a + b
        count += 1

# 使用
for num in fibonacci(10):
    print(num, end=' ')
# 输出:0 1 1 2 3 5 8 13 21 34

生成器的关键特性:

  • 惰性求值:只在需要时计算下一个值
  • 状态保持:每次 yield 后暂停,下次从暂停处继续
  • 内存 高效:不存储所有值,只保存当前状态

生成器表达式

类似于列表推导式,但使用圆括号:

# 列表推导式:立即创建完整列表
squares_list = [x**2 for x in range(1000000)]  # 占用大量内存

# 生成器表达式:惰性生成
squares_gen = (x**2 for x in range(1000000))  # 几乎不占内存

# 使用
print(next(squares_gen))  # 0
print(next(squares_gen))  # 1
print(sum(x**2 for x in range(1000)))  # 可直接用于函数

实用场景

场景一:流式处理大文件

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

# 处理 10GB 的日志文件
for line in read_large_file('huge_log.txt'):
    if 'ERROR' in line:
        print(line)

场景二:分页数据获取

def fetch_paginated_data(api_url, page_size=100):
    """分页获取 API 数据"""
    page = 1
    while True:
        # 模拟 API 请求
        response = {
            'data': [f'item_{page}_{i}' for i in range(page_size)],
            'has_more': page < 5  # 模拟还有更多数据
        }
        
        for item in response['data']:
            yield item
        
        if not response['has_more']:
            break
        page += 1

# 使用
for item in fetch_paginated_data('https://api.example.com/data'):
    process(item)  # 处理每个项目

场景三:管道式数据处理

def data_pipeline(data):
    """构建数据处理管道"""
    # 过滤
    filtered = (x for x in data if x > 0)
    # 转换
    squared = (x**2 for x in filtered)
    # 再次过滤
    result = (x for x in squared if x < 100)
    return result

# 使用
data = [-5, 3, -1, 7, 15, 2]
for value in data_pipeline(data):
    print(value)  # 输出:9, 49, 4

高级技巧

send():向生成器发送值

生成器不仅返回值,还可以接收值:

def accumulator():
    """累加器生成器"""
    total = 0
    while True:
        value = yield total
        if value is not None:
            total += value

# 使用
acc = accumulator()
print(next(acc))      # 启动生成器,输出:0
print(acc.send(10))   # 发送 10,输出:10
print(acc.send(20))   # 发送 20,输出:30
print(acc.send(5))    # 发送 5,输出:35

throw():向生成器抛出异常

def safe_divisor():
    """安全的除数生成器"""
    try:
        yield 2
        yield 4
        yield 0  # 危险的除数
        yield 8
    except ZeroDivisionError:
        print("捕获到除零异常")
        yield 1  # 提供默认值

gen = safe_divisor()
print(next(gen))  # 2
print(next(gen))  # 4

# 在获取 0 之前抛出异常
try:
    gen.throw(ZeroDivisionError)
except ZeroDivisionError:
    print("外部捕获异常")

委托生成器:yield from

yield from 可以委托给另一个生成器,简化嵌套迭代:

def sub_generator():
    yield 'a'
    yield 'b'
    yield 'c'

def main_generator():
    yield from sub_generator()  # 委托
    yield 'd'
    yield 'e'

# 使用
for item in main_generator():
    print(item)  # 输出:a, b, c, d, e

实际应用场景 - 展平嵌套列表:

def flatten(nested_list):
    """递归展平嵌套列表"""
    for item in nested_list:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item

# 使用
nested = [1, [2, 3, [4, 5]], 6, [7, [8, 9]]]
flat = list(flatten(nested))
print(flat)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

生成器 vs 列表:性能对比

import sys
import time

# 列表:立即创建
list_start = time.time()
list_result = [x**2 for x in range(1000000)]
list_time = time.time() - list_start
list_memory = sys.getsizeof(list_result)

# 生成器:惰性创建
gen_start = time.time()
gen_result = (x**2 for x in range(1000000))
gen_time = time.time() - gen_start
gen_memory = sys.getsizeof(gen_result)

print(f"列表 - 时间:{list_time:.4f}s, 内存:{list_memory} 字节")
print(f"生成器 - 时间:{gen_start:.4f}s, 内存:{gen_memory} 字节")
# 生成器内存占用通常只有列表的几百分之一

总结

生成器和迭代器是 Python 处理数据的强大工具:

核心优势:

  • 内存效率:惰性求值,按需计算,处理海量数据不爆内存
  • 代码简洁:用少量代码表达复杂的数据流逻辑
  • 组合性强:生成器可以链式组合,构建清晰的数据管道
  • 状态保持:自动保存执行状态,无需手动管理

使用建议:

  1. 处理大文件或大数据集时,优先使用生成器
  2. 需要无限序列时(如随机数流),生成器是唯一选择
  3. 构建数据处理管道时,用生成器表达式替代中间列表
  4. 需要双向通信时,使用 send()throw()

掌握生成器和迭代器,你将能够编写更加高效、优雅的 Python 代码。记住:不是所有数据都需要立即加载,有时"懒"一点反而更好。