1. 为什么我们需要迭代器和生成器?
记得我刚学Python那会儿,处理一个10GB的日志文件时,直接readlines()加载到内存,结果程序直接崩溃。这就是迭代器和生成器要解决的核心问题——内存效率。
Python中的迭代器协议由两个方法组成:
__iter__():返回迭代器对象本身__next__():返回下一个元素,没有元素时抛出StopIteration异常
python复制class MyRange:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
value = self.current
self.current += 1
return value
for num in MyRange(1, 5):
print(num) # 输出1,2,3,4
关键理解:迭代器是实现了迭代器协议的对象,而生成器是创建迭代器的快捷方式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 生成器的两种创建方式
2.1 生成器函数(yield关键字)
这是最常用的生成器创建方式。当函数包含yield语句时,它自动成为生成器函数:
python复制def countdown(n):
print("Starting countdown!")
while n > 0:
yield n
n -= 1
print("Blast off!")
# 使用示例
for i in countdown(5):
print(i)
执行过程揭秘:
- 调用countdown(5)时不会立即执行函数体,而是返回生成器对象
- 每次for循环调用next()时,执行到yield暂停
- 下次迭代从yield之后继续执行
2.2 生成器表达式
类似列表推导式,但使用圆括号:
python复制# 列表推导式(立即计算)
squares_list = [x**2 for x in range(1000000)] # 占用大量内存
# 生成器表达式(惰性计算)
squares_gen = (x**2 for x in range(1000000)) # 几乎不占内存
性能对比:处理1000万数据时,生成器表达式内存占用始终<1MB,而列表推导式可能消耗800MB+。
3. 迭代器与生成器的进阶技巧
3.1 双向通信:send()方法
生成器可以通过send()接收外部传入的值:
python复制def accumulator():
total = 0
while True:
value = yield total
if value is None:
break
total += value
gen = accumulator()
next(gen) # 启动生成器
print(gen.send(10)) # 输出10
print(gen.send(20)) # 输出30
3.2 生成器委托(yield from)
Python 3.3+引入了yield from语法,用于生成器嵌套:
python复制def chain(*iterables):
for it in iterables:
yield from it
list(chain('ABC', 'DEF')) # ['A','B','C','D','E','F']
3.3 无限序列生成
生成器非常适合表示无限序列:
python复制def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib)) # 输出前10个斐波那契数
4. 实战中的坑与最佳实践
4.1 常见陷阱
-
重复消费问题:
python复制gen = (x for x in range(3)) print(list(gen)) # [0,1,2] print(list(gen)) # [] 生成器已耗尽 -
过早耗尽:
python复制def get_numbers(): yield 1 yield 2 nums = get_numbers() print(sum(nums)) # 3 print(sum(nums)) # 0
4.2 性能优化技巧
-
管道处理大文件:
python复制def read_large_file(file_path): with open(file_path) as f: for line in f: yield line.strip() def filter_lines(lines, keyword): for line in lines: if keyword in line: yield line # 使用管道 lines = read_large_file('huge.log') filtered = filter_lines(lines, 'ERROR') for error_line in filtered: process_error(error_line) -
内存分析工具:
python复制import sys gen = (x for x in range(1000000)) lst = [x for x in range(1000000)] print(sys.getsizeof(gen)) # 通常112字节 print(sys.getsizeof(lst)) # 约9MB
5. 与标准库的深度结合
5.1 itertools模块的妙用
python复制from itertools import islice, count
# 无限序列切片
first_10 = islice(count(), 10) # 0-9
# 排列组合
from itertools import permutations
for p in permutations('ABC', 2):
print(p) # ('A','B'), ('A','C')...
5.2 contextlib中的生成器上下文管理器
python复制from contextlib import contextmanager
@contextmanager
def timed_block(label):
start = time.time()
try:
yield
finally:
print(f"{label} took {time.time()-start:.2f}s")
with timed_block("processing"):
# 执行耗时操作
time.sleep(1)
6. 异步生成器(Python 3.6+)
现代Python中,生成器与async/await结合:
python复制async def async_counter(n):
for i in range(n):
yield i
await asyncio.sleep(0.1)
async def main():
async for num in async_counter(5):
print(num)
asyncio.run(main())
我在实际项目中发现,当处理IO密集型任务时,异步生成器可以显著提高吞吐量。比如爬虫场景中,用异步生成器管理URL队列,配合aiohttp可以实现高效的并发请求。
