1. 迭代器:Python中的隐形传送带
想象你站在一条食品加工流水线前,传送带正源源不断地运送着待处理的食材。你不需要知道这条传送带有多长,也不需要一次性把所有食材都堆在面前,只需要在需要时从传送带上取下当前到达的食材进行处理——这就是Python迭代器(Iterator)的生动写照。
在Python中,迭代器是一个可以记住遍历位置的对象,它实现了两个特殊方法:__iter__()和__next__()。与直接把所有元素加载到内存的列表不同,迭代器采用"按需供给"的工作模式,这种特性在处理大规模数据时尤为珍贵。比如读取几个GB的日志文件时,使用迭代器可以避免内存爆满的尴尬:
python复制# 传统列表式读取(危险!)
with open('huge.log') as f:
lines = f.readlines() # 一次性加载所有行到内存
# 迭代器式读取(安全)
with open('huge.log') as f:
for line in f: # 文件对象本身就是迭代器
process(line)
2. 迭代协议:揭开魔法背后的机制
2.1 迭代器与可迭代对象的区别
新手常会混淆"可迭代对象"(Iterable)和"迭代器"(Iterator)这两个概念。简单来说:
- 可迭代对象:实现了
__iter__()方法的对象,能返回一个迭代器(如列表、元组、字典) - 迭代器:实现了
__iter__()和__next__()方法的对象(如文件对象、生成器)
可以用collections.abc模块进行类型验证:
python复制from collections.abc import Iterable, Iterator
lst = [1, 2, 3]
print(isinstance(lst, Iterable)) # True
print(isinstance(lst, Iterator)) # False
lst_iter = iter(lst)
print(isinstance(lst_iter, Iterator)) # True
2.2 手动实现迭代器
理解迭代器最好的方式就是自己实现一个。下面我们创建一个生成斐波那契数列的迭代器:
python复制class Fibonacci:
def __init__(self, max_count):
self.max_count = max_count
self.count = 0
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
if self.count >= self.max_count:
raise StopIteration
value = self.a
self.a, self.b = self.b, self.a + self.b
self.count += 1
return value
# 使用示例
for num in Fibonacci(10):
print(num, end=' ') # 输出:0 1 1 2 3 5 8 13 21 34
注意:迭代器是一次性消费品,遍历结束后再次迭代不会得到任何元素。如果需要重复使用,应该重新创建迭代器或使用可迭代对象。
3. 迭代器的高级应用场景
3.1 处理无限序列
迭代器的惰性求值特性使其非常适合表示无限序列。下面是一个无限质数生成器的实现:
python复制import math
class PrimeGenerator:
def __init__(self):
self.current = 2
def __iter__(self):
return self
def __next__(self):
while True:
if self.is_prime(self.current):
prime = self.current
self.current += 1
return prime
self.current += 1
def is_prime(self, n):
if n < 2:
return False
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
# 使用itertools.islice获取有限数量的元素
from itertools import islice
first_10_primes = list(islice(PrimeGenerator(), 10))
print(first_10_primes) # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
3.2 内存高效的数据管道
在处理数据转换流水线时,使用迭代器可以构建内存友好的处理链。例如实现一个数据处理管道:
python复制def read_large_file(filename):
with open(filename) as f:
for line in f:
yield line.strip()
def filter_comments(lines):
for line in lines:
if not line.startswith('#'):
yield line
def parse_numbers(lines):
for line in lines:
yield [float(x) for x in line.split()]
# 构建处理管道
lines = read_large_file('data.txt')
clean_lines = filter_comments(lines)
number_sequences = parse_numbers(clean_lines)
# 实际处理时才会逐条执行
for seq in number_sequences:
process(seq)
这种链式处理方式确保任何时候内存中只保存当前处理的数据项,而不是整个数据集。
4. 迭代器工具库:itertools的妙用
Python标准库中的itertools模块提供了大量强大的迭代器工具,下面介绍几个常用"武器":
4.1 无限迭代器
python复制import itertools
# 计数器(从10开始,步长0.5)
for num in itertools.count(10, 0.5):
if num > 15: break
print(num) # 10, 10.5, 11, 11.5,...
# 循环播放
for item in itertools.cycle(['A', 'B', 'C']):
if len(result) > 6: break
print(item) # A, B, C, A, B, C,...
4.2 组合迭代器
python复制# 排列组合(不考虑顺序)
combinations = itertools.combinations('ABCD', 2)
print(list(combinations))
# [('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
# 排列(考虑顺序)
permutations = itertools.permutations('ABC', 2)
print(list(permutations))
# [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
4.3 数据分组与筛选
python复制# 按条件分组
data = [('apple', 'fruit'), ('carrot', 'vegetable'),
('banana', 'fruit'), ('lettuce', 'vegetable')]
grouped = itertools.groupby(sorted(data, key=lambda x: x[1]),
lambda x: x[1])
for key, group in grouped:
print(f"{key}: {list(group)}")
# 输出:
# fruit: [('apple', 'fruit'), ('banana', 'fruit')]
# vegetable: [('carrot', 'vegetable'), ('lettuce', 'vegetable')]
5. 迭代器性能优化与陷阱规避
5.1 迭代器 vs 生成器表达式
虽然生成器表达式(Generator Expression)看起来像列表推导式,但它们实际上是创建匿名迭代器的快捷方式:
python复制# 列表推导式(立即计算,占用内存)
squares_list = [x**2 for x in range(1000000)]
# 生成器表达式(惰性计算,节省内存)
squares_gen = (x**2 for x in range(1000000))
import sys
print(sys.getsizeof(squares_list)) # 约8448728字节
print(sys.getsizeof(squares_gen)) # 约112字节
5.2 常见陷阱与解决方案
陷阱1:已耗尽的迭代器重复使用
python复制numbers = iter([1, 2, 3])
print(list(numbers)) # [1, 2, 3]
print(list(numbers)) # [] (迭代器已耗尽)
解决方案:如果需要多次遍历,要么重新创建迭代器,要么先将数据转换为列表:
python复制data = [1, 2, 3]
# 方法1:每次重新创建迭代器
for _ in range(2):
print(list(iter(data)))
# 方法2:转换为列表(如果数据量不大)
numbers = list(data)
陷阱2:在迭代过程中修改容器
python复制words = ['hello', 'world', 'python']
for word in words:
if len(word) > 5:
words.remove(word) # 危险操作!
解决方案:创建副本或使用列表推导式:
python复制# 安全方式1:迭代副本
for word in words.copy():
if len(word) > 5:
words.remove(word)
# 安全方式2:使用列表推导式创建新列表
words = [word for word in words if len(word) <= 5]
6. 实战:构建自定义数据流处理器
让我们综合运用迭代器知识,实现一个CSV文件处理器,它可以:
- 按行流式读取大文件
- 自动跳过空行和注释行
- 类型转换字段值
- 过滤不符合条件的记录
python复制import csv
from typing import Iterator, Any
class CSVProcessor:
def __init__(self, filename: str):
self.filename = filename
def _read_rows(self) -> Iterator[list[str]]:
with open(self.filename, newline='') as f:
reader = csv.reader(f)
for row in reader:
if not row or row[0].startswith('#'):
continue
yield row
def _convert_types(self, rows: Iterator[list[str]]) -> Iterator[list[Any]]:
type_converters = [int, float, str] # 示例转换函数
for row in rows:
try:
yield [converter(val) for converter, val in zip(type_converters, row)]
except ValueError:
continue # 跳过类型转换失败的行
def _filter_rows(self, rows: Iterator[list[Any]],
condition: callable) -> Iterator[list[Any]]:
for row in rows:
if condition(row):
yield row
def process(self, condition: callable = lambda x: True) -> Iterator[list[Any]]:
rows = self._read_rows()
converted = self._convert_types(rows)
filtered = self._filter_rows(converted, condition)
return filtered
# 使用示例
processor = CSVProcessor('data.csv')
# 只获取第二列值大于10的记录
for record in processor.process(lambda r: r[1] > 10):
print(record)
这种设计模式将数据处理分解为多个可组合的迭代器阶段,每个阶段只关注单一职责,既保证了代码清晰度,又实现了内存高效处理。
