Python高效编程:生成器与迭代器深度实战,内存优化50%的秘诀
导语: 处理百万级数据时,用列表全部加载会直接OOM崩溃,而生成器却能以极低内存流畅跑完。生成器是Python高效编程的核心武器,也是大厂面试必考题。本文从迭代协议原理出发,深入讲解 yield、yield from、生成器表达式、惰性计算、协程基础,结合真实大数据处理场景,让你彻底掌握Python内存优化的核心手段。
一、迭代器协议基础
Python的迭代协议由两个方法构成:
# 自定义迭代器
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self # 返回迭代器对象自身
def __next__(self):
if self.current <= 0:
raise StopIteration # 终止信号
value = self.current
self.current -= 1
return value
counter = CountDown(5)
for n in counter:
print(n) # 5 4 3 2 1
# 手动迭代
it = iter([1, 2, 3])
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# next(it) # StopIteration
二、生成器函数:yield 的本质
生成器函数用 yield 替代 return,每次调用 next() 时恢复执行到下一个 yield:
def fibonacci():
"""无限斐波那契数列生成器"""
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 取前10个
fib = fibonacci()
first_10 = [next(fib) for _ in range(10)]
print(first_10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# 用 itertools.islice 截取
from itertools import islice
print(list(islice(fibonacci(), 20)))
2.1 内存对比实验
import sys
# 列表:全量加载到内存
big_list = [x ** 2 for x in range(1_000_000)]
print(f"列表内存占用: {sys.getsizeof(big_list) / 1024 / 1024:.2f} MB")
# 约 7.63 MB
# 生成器:惰性计算,几乎不占内存
big_gen = (x ** 2 for x in range(1_000_000))
print(f"生成器内存占用: {sys.getsizeof(big_gen)} bytes")
# 仅 112 bytes!
三、生成器实战:大文件逐行处理
def read_large_file(filepath, chunk_size=1024):
"""逐块读取大文件,避免OOM"""
with open(filepath, 'r', encoding='utf-8') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
yield chunk
def process_log_file(filepath):
"""生成器管道:逐行处理GB级日志"""
with open(filepath, 'r', encoding='utf-8') as f:
for line in f: # 文件对象本身就是迭代器
line = line.strip()
if not line or line.startswith('#'):
continue
yield line
def parse_error_lines(lines):
"""过滤错误行"""
for line in lines:
if 'ERROR' in line:
yield line
def extract_timestamps(lines):
"""提取时间戳"""
import re
pattern = re.compile(r'\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}')
for line in lines:
match = pattern.search(line)
if match:
yield match.group(), line
# 生成器管道:组合使用
def analyze_log(filepath):
lines = process_log_file(filepath)
error_lines = parse_error_lines(lines)
timestamped = extract_timestamps(error_lines)
for timestamp, line in timestamped:
print(f"[{timestamp}] {line[:80]}")
四、yield from:委托生成器
Python 3.3+ 引入 yield from,简化生成器嵌套:
# 不用 yield from 的嵌套写法
def chain_old(*iterables):
for it in iterables:
for item in it:
yield item
# 使用 yield from(推荐)
def chain_new(*iterables):
for it in iterables:
yield from it
result = list(chain_new([1, 2], [3, 4], [5, 6]))
# [1, 2, 3, 4, 5, 6]
# 递归生成器:展开任意嵌套列表
def flatten(nested):
for item in nested:
if isinstance(item, (list, tuple)):
yield from flatten(item)
else:
yield item
deep = [1, [2, [3, 4], 5], [6, 7]]
print(list(flatten(deep))) # [1, 2, 3, 4, 5, 6, 7]
五、生成器表达式与 itertools 工具链
import itertools
# 生成器表达式(括号而非方括号)
squares_gen = (x**2 for x in range(10))
total = sum(x**2 for x in range(10)) # 直接传入 sum,无需列表
# itertools.chain:连接多个迭代器
from itertools import chain
combined = list(chain([1, 2, 3], [4, 5, 6], range(7, 10)))
# itertools.groupby:分组
from itertools import groupby
data = sorted([
{'name': 'Alice', 'dept': 'Engineering'},
{'name': 'Bob', 'dept': 'Marketing'},
{'name': 'Charlie', 'dept': 'Engineering'},
], key=lambda x: x['dept'])
for dept, members in groupby(data, key=lambda x: x['dept']):
print(f"{dept}: {[m['name'] for m in members]}")
# itertools.takewhile / dropwhile
from itertools import takewhile, dropwhile
numbers = [1, 3, 5, 2, 7, 9]
less_than_5 = list(takewhile(lambda x: x < 5, numbers))
# [1, 3] —— 遇到不满足条件立即停止
六、send() 与双向通信生成器
def accumulator():
"""接收数值,持续累加"""
total = 0
while True:
value = yield total # yield 既发出值也接收值
if value is None:
break
total += value
gen = accumulator()
next(gen) # 启动生成器(必须先 next 一次)
print(gen.send(10)) # 10
print(gen.send(20)) # 30
print(gen.send(5)) # 35
七、开发痛点与报错避坑指南
| StopIteration 意外抛出 | 生成器已耗尽仍调用 next() | 使用 for 循环或 next(gen, default) |
| 生成器只能遍历一次 | 惰性特性 | 需多次遍历时转换为列表或重建生成器 |
| send() 报 TypeError | 未先调用 next() 启动生成器 | 首次调用 next(gen) 或 gen.send(None) |
| 生成器内异常被吞噬 | 生成器内部未处理异常 | 使用 gen.throw() 注入异常并捕获 |
# next() 安全写法
gen = (x for x in range(3))
print(next(gen, "已耗尽")) # 0
print(next(gen, "已耗尽")) # 1
print(next(gen, "已耗尽")) # 2
print(next(gen, "已耗尽")) # 已耗尽(不抛异常)
八、全文总结
- 生成器 vs 列表:百万级数据处理首选生成器,内存节省 99%+
- 生成器管道:多个生成器串联,实现零拷贝的数据处理流水线
- yield from:简化委托生成器,支持递归展开嵌套结构
- itertools:Python内置的生成器工具箱,组合使用威力倍增
九、技术进阶展望
- 深入 asyncio 异步编程 —— 生成器是协程的基础
- 研究 PEP 342(增强生成器)和 PEP 380(yield from 规范)
- 学习 aiofiles 实现异步大文件处理




