引言
在 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 处理数据的强大工具:
核心优势:
- 内存效率:惰性求值,按需计算,处理海量数据不爆内存
- 代码简洁:用少量代码表达复杂的数据流逻辑
- 组合性强:生成器可以链式组合,构建清晰的数据管道
- 状态保持:自动保存执行状态,无需手动管理
使用建议:
- 处理大文件或大数据集时,优先使用生成器
- 需要无限序列时(如随机数流),生成器是唯一选择
- 构建数据处理管道时,用生成器表达式替代中间列表
- 需要双向通信时,使用
send()和throw()
掌握生成器和迭代器,你将能够编写更加高效、优雅的 Python 代码。记住:不是所有数据都需要立即加载,有时"懒"一点反而更好。