第32课 - 迭代器协议(__iter__, __next__)
学习目标
完成本课学习后,你将能够:
- 理解Python中"迭代器"与"可迭代对象"的区别。
- 掌握并实现迭代器协议,包括
__iter__()和__next__()两个魔法方法。 - 理解
for循环的内部工作原理。 - 能够根据需求,为自定义数据类型创建自定义的迭代器。
- 知道如何正确抛出
StopIteration异常以结束迭代。
核心概念
在Python中,我们常常需要遍历(迭代)一个容器中的元素,比如列表、元组、字典。for 循环让这一切变得非常简单。其背后的机制,就是迭代器协议。
1. 可迭代对象 (Iterable) vs. 迭代器 (Iterator)
你可以把 可迭代对象 想象成一个"书架",上面放着一系列书(数据)。而 迭代器 就像一个"书签",它知道当前读到了哪一页,并且能够帮你翻到下一页。
- 可迭代对象 (Iterable): 任何实现了
__iter__()方法的对象。这个方法必须返回一个迭代器对象。常见的list,str,dict,tuple都是可迭代对象。 - 迭代器 (Iterator): 任何实现了
__iter__()和__next__()两个方法的对象。__iter__(): 返回迭代器对象自身。因为迭代器本身也是一个可迭代对象(你可以再次对它调用iter())。__next__(): 返回序列中的下一个元素。当没有更多元素时,必须抛出StopIteration异常。
关系图: 一个可迭代对象(书架)可以生成一个迭代器(书签)。迭代器负责记住状态并产出数据。
2. for 循环的本质
当你写 for item in some_iterable: 时,Python在后台做了以下事情:
- 调用
iter(some_iterable),这会调用some_iterable.__iter__(),获取一个迭代器对象。 - 开始循环,不断调用迭代器的
next(iterator)方法(即调用__next__())。 - 将返回的值赋值给
item。 - 当
__next__()抛出StopIteration异常时,循环自动结束。
代码示例
示例1:一个简单的倒计时迭代器
我们创建一个从某个数字倒数到0的迭代器。
class Countdown:
"""一个从start倒数到0的迭代器"""
def __init__(self, start):
self.current = start
def __iter__(self):
# 迭代器必须返回自身
return self
def __next__(self):
# 如果当前数字小于0,则停止迭代
if self.current < 0:
raise StopIteration
# 保存当前值用于返回
value = self.current
# 递减计数器
self.current -= 1
return value
# 使用自定义迭代器
print("倒计时开始:")
for num in Countdown(5):
print(num, end=' ')
print() # 换行
# 也可以手动模拟for循环
print("\n手动模拟for循环:")
countdown_iter = Countdown(3)
print(next(countdown_iter)) # 3
print(next(countdown_iter)) # 2
print(next(countdown_iter)) # 1
print(next(countdown_iter)) # 0
# print(next(countdown_iter)) # 这行会引发 StopIteration
输出:
倒计时开始:
5 4 3 2 1 0
手动模拟for循环:
3
2
1
0
示例2:一个可迭代的Range类
我们创建一个类似内置 range 的类。它本身是一个可迭代对象,每次调用 __iter__ 都会返回一个新的迭代器,这样可以实现多次遍历。
class MyRange:
"""一个可自定义的Range类,演示可迭代对象与迭代器的分离"""
def __init__(self, start, end, step=1):
self.start = start
self.end = end
self.step = step
def __iter__(self):
# 返回一个新的迭代器实例,这样每次for循环都是一个全新的开始
return MyRangeIterator(self.start, self.end, self.step)
class MyRangeIterator:
"""MyRange的迭代器类"""
def __init__(self, start, end, step):
self.current = start
self.end = end
self.step = step
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
value = self.current
self.current += self.step
return value
# 使用我们的自定义Range
my_range = MyRange(0, 10, 2)
print("第一次遍历:")
for i in my_range:
print(i, end=' ')
print()
print("第二次遍历:")
for i in my_range:
print(i, end=' ')
print()
# 内置的range可以多次遍历,因为它是一个可迭代对象,每次都会生成新的迭代器。
# 我们的MyRange类也做到了这一点!
输出:
第一次遍历:
0 2 4 6 8
第二次遍历:
0 2 4 6 8
实践练习
练习1:无限计数器
创建一个名为 InfiniteCounter 的迭代器。它从给定的起始值开始,每次调用 __next__ 返回当前值并递增1。这是一个无限迭代器,所以你需要谨慎使用(例如在循环中设置 break 条件)。
- 要求:实现
__iter__和__next__方法。 - 预期输出:创建一个起始值为5的计数器,打印前10个数字。
# 你的代码应该实现:
# counter = InfiniteCounter(5)
# for i, num in enumerate(counter):
# if i >= 10:
# break
# print(num, end=' ')
# 应输出:5 6 7 8 9 10 11 12 13 14
练习2:链表迭代器
假设我们已经有一个简单的单链表节点类:
class Node:
def __init__(self, value, next_node=None):
self.value = value
self.next = next_node
请创建一个 LinkedListIterator 类,它能够迭代一个由 Node 对象组成的链表,按顺序返回每个节点的值。
- 要求:实现迭代器协议。链表的头节点将作为迭代器构造函数的参数。
- 预期输出:
# 构建链表: 1 -> 2 -> 3 -> None
head = Node(1, Node(2, Node(3)))
# 你的迭代器应该能完成:
# for value in LinkedListIterator(head):
# print(value)
# 输出:1 2 3
常见错误
-
混淆可迭代对象和迭代器:忘记
__iter__()和__next__()的区别。可迭代对象只需要__iter__,迭代器需要两者。# 错误:让一个类同时是迭代器和可迭代对象,但__iter__没有返回self class BadIterable: def __init__(self, data): self.data = data self.index = 0 def __iter__(self): return self # 这里返回self是正确的,因为这个类本身也是迭代器 def __next__(self): # ... 省略实现 # 这个类可以工作,但如果想多次遍历就不行了,因为状态(self.index)会保留。 # 最佳实践是将状态保存在迭代器类中,见示例2。 -
忘记抛出StopIteration:当没有更多元素时,必须显式
raise StopIteration,否则会造成无限循环。# 错误示例 def __next__(self): if self.current >= self.end: # 这里什么都不做,程序会陷入死循环或者返回None # 正确做法: raise StopIteration return None # 这是错误的! # ... -
在迭代中修改集合:在
for循环迭代一个列表的同时,尝试添加或删除元素,会导致RuntimeError。你应该迭代列表的副本或使用其他方法。my_list = [1, 2, 3, 4] # 错误:在迭代时删除元素 for item in my_list: if item % 2 == 0: my_list.remove(item) # 这会引发RuntimeError # 正确做法:迭代一个副本 for item in my_list[:]: # my_list[:] 创建了一个浅拷贝 if item % 2 == 0: my_list.remove(item)
小结
- 迭代器协议由两个魔法方法定义:
__iter__()和__next__()。 - 可迭代对象:实现了
__iter__()方法,该方法返回一个迭代器。 - 迭代器:同时实现了
__iter__()(返回自身)和__next__()方法。__next__()负责产出下一个值,并在耗尽时抛出StopIteration。 for循环在底层是通过反复调用__next__()并捕获StopIteration来工作的。- 实现迭代器协议使你的自定义对象能够与
for循环、list()、sum()等所有依赖迭代的Python功能无缝协作。 - 理解迭代器协议是理解下一课生成器的基础。生成器是一种更简洁、高效的创建迭代器的方式。