Python中的迭代器(Iterator)是一个让很多初学者感到困惑却又无处不在的概念。简单来说,迭代器就是实现了迭代协议的对象,它能够让我们逐个访问容器中的元素而不必暴露底层实现。想象你有一个魔法盒子,每次打开都只能取出一个物品,直到盒子变空为止——这就是迭代器的工作方式。
在Python中,迭代器必须实现两个特殊方法:__iter__()和__next__()。__iter__()返回迭代器对象本身,而__next__()则负责返回下一个元素,并在没有元素时抛出StopIteration异常。这种设计使得迭代器具有以下重要特性:
python复制# 一个简单的迭代器示例
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
num = self.current
self.current -= 1
return num
# 使用示例
for num in CountDown(5):
print(num) # 输出5,4,3,2,1
注意:在Python中,迭代器对象本身也是可迭代的(实现了
__iter__()),但可迭代对象不一定是迭代器。这是初学者常混淆的概念。
当我们使用for item in iterable这样的语句时,Python解释器实际上执行了以下步骤:
iter()函数获取iterable对象的迭代器__next__()方法获取下一个值这个过程可以用以下伪代码表示:
python复制# for循环的等价实现
iterator = iter(iterable) # 调用__iter__获取迭代器
while True:
try:
item = next(iterator) # 调用__next__获取元素
# 执行循环体代码
except StopIteration:
break
这种设计使得Python的for循环极其灵活,可以与任何实现了迭代协议的对象配合工作。这也是为什么Python中如此多的对象都支持for循环遍历——从列表、字典到文件对象、生成器,甚至是自定义类。
Python中大多数内置容器都是可迭代的但不是迭代器:
iter(lst)返回一个列表迭代器iter(dict)等价于iter(dict.keys())python复制# 内置类型的迭代行为示例
numbers = [1, 2, 3]
iterator = iter(numbers)
print(next(iterator)) # 1
print(next(iterator)) # 2
文件对象本身就是迭代器,每次迭代返回一行内容:
python复制with open('data.txt') as f:
for line in f: # 文件对象是自身的迭代器
print(line.strip())
重要提示:文件对象只能迭代一次,因为它们是迭代器而非简单的可迭代对象。如果需要多次读取,需要重新打开文件或使用
seek(0)重置文件指针。
生成器函数是创建迭代器最便捷的方式之一。使用yield关键字的函数会自动成为生成器函数,调用时返回一个生成器对象(一种特殊的迭代器):
python复制def fibonacci(limit):
a, b = 0, 1
while a < limit:
yield a
a, b = b, a + b
# 使用示例
for num in fibonacci(100):
print(num) # 0,1,1,2,3,5,8,13,21,34,55,89
生成器特别适合处理大型数据集或无限序列,因为它们只在需要时生成值,不会一次性占用大量内存。
Python标准库中的itertools模块提供了大量操作迭代器的实用函数:
count(), cycle(), repeat()product(), permutations(), combinations()filterfalse(), dropwhile(), takewhile()groupby()python复制from itertools import islice, count
# 创建一个无限计数器,但只取前10个
for num in islice(count(10), 10):
print(num) # 10,11,12,...,19
通过生成器表达式和itertools.chain,可以创建复杂的迭代管道:
python复制import itertools
# 合并多个迭代器
chain = itertools.chain([1,2], 'abc', (x for x in range(3)))
list(chain) # [1,2,'a','b','c',0,1,2]
# 使用生成器表达式过滤
numbers = (x for x in range(100) if x % 7 == 0)
对于更复杂的需求,可以实现状态更丰富的迭代器:
python复制class WindowIterator:
"""滑动窗口迭代器,返回连续的n个元素"""
def __init__(self, iterable, window_size):
self.iterator = iter(iterable)
self.window = []
self.window_size = window_size
# 初始化窗口
for _ in range(window_size):
try:
self.window.append(next(self.iterator))
except StopIteration:
break
def __iter__(self):
return self
def __next__(self):
if len(self.window) < self.window_size:
raise StopIteration
result = tuple(self.window)
try:
self.window.pop(0)
self.window.append(next(self.iterator))
except StopIteration:
self.window.pop(0)
return result
# 使用示例
for window in WindowIterator([1,2,3,4,5], 3):
print(window) # (1,2,3), (2,3,4), (3,4,5)
迭代器的主要优势在于内存效率。考虑处理一个10GB的日志文件:
python复制# 低效方式:一次性读取
with open('huge.log') as f:
lines = f.readlines() # 内存爆炸!
for line in lines:
process(line)
# 高效方式:使用迭代器
with open('huge.log') as f:
for line in f: # 每次只读取一行
process(line)
虽然迭代器很强大,但在某些情况下直接使用列表可能更好:
问题1:迭代器耗尽后无法重用
python复制numbers = iter([1,2,3])
list(numbers) # [1,2,3]
list(numbers) # [] 迭代器已耗尽
解决方案:存储原始数据而非迭代器,或使用itertools.tee创建多个迭代器
问题2:在迭代过程中修改容器
python复制d = {'a':1, 'b':2}
for k in d:
del d[k] # RuntimeError: 字典在迭代时改变大小
解决方案:先创建副本for k in list(d):或收集要修改的键最后统一处理
问题3:无限迭代器导致程序挂起
python复制from itertools import count
for i in count(): # 无限循环
print(i)
解决方案:总是为无限迭代器设置终止条件,如islice(count(), 100)
处理大型数据库查询时,使用迭代器可以避免一次性加载所有结果:
python复制import sqlite3
class DBResultIterator:
def __init__(self, db_path, query, chunk_size=100):
self.conn = sqlite3.connect(db_path)
self.cursor = self.conn.cursor()
self.cursor.execute(query)
self.chunk_size = chunk_size
def __iter__(self):
return self
def __next__(self):
results = self.cursor.fetchmany(self.chunk_size)
if not results:
self.cursor.close()
self.conn.close()
raise StopIteration
return results
# 使用示例
for chunk in DBResultIterator('data.db', 'SELECT * FROM big_table'):
process_chunk(chunk)
构建数据处理管道时,迭代器可以实现各阶段的解耦:
python复制def read_logs(file_path):
with open(file_path) as f:
for line in f:
yield line.strip()
def filter_errors(logs):
for log in logs:
if 'ERROR' in log:
yield log
def extract_message(logs):
for log in logs:
yield log.split(':', 2)[-1]
# 构建处理管道
logs = read_logs('app.log')
errors = filter_errors(logs)
messages = extract_message(errors)
for msg in messages:
print(msg)
当实现自定义集合类时,合理设计迭代行为很重要:
python复制class TreeNode:
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
def __iter__(self):
return self.inorder()
def inorder(self):
if self.left:
yield from self.left.inorder()
yield self.value
if self.right:
yield from self.right.inorder()
# 使用示例
root = TreeNode(1,
TreeNode(2,
TreeNode(4),
TreeNode(5)),
TreeNode(3))
for value in root:
print(value) # 4,2,5,1,3
随着Python语言的演进,迭代器协议也在不断优化。Python 3.10中引入的match语句为迭代器处理提供了新模式:
python复制from collections.abc import Iterator
def process_iterable(obj):
match obj:
case Iterator():
print("处理迭代器...")
case _ if isinstance(obj, (list, tuple)):
print("处理序列...")
case _:
print("未知类型")
PEP 584在Python 3.9中为字典添加了联合操作符(|),这也影响了字典迭代器的行为。此外,异步迭代器(async iterator)的引入为异步编程提供了强大的迭代能力:
python复制import asyncio
class AsyncCounter:
def __init__(self, limit):
self.limit = limit
self.current = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.current >= self.limit:
raise StopAsyncIteration
await asyncio.sleep(0.1)
self.current += 1
return self.current
async def main():
async for num in AsyncCounter(5):
print(num) # 1,2,3,4,5
asyncio.run(main())
在实际开发中,理解迭代器的内部机制不仅能帮助我们编写更高效的代码,还能让我们更好地利用Python的各种高级特性。从简单的for循环到复杂的异步数据处理,迭代器模式都是Python编程中不可或缺的核心概念。