迭代器模式练习——自定义集合、逆序与过滤
按《Python 设计模式——迭代器模式》的建议,通过三道练习巩固:① 自定义集合实现 __iter__/__next__ 并用 for 遍历;② 为同一集合提供逆序迭代器;③ 实现一个过滤迭代器。每步都有完整可运行代码和验证要点。
练习一:自定义集合 + for 遍历(iter 与 next)
目的
体会 集合 与 迭代器 的分离:集合只负责存数据并返回迭代器,客户通过 for 遍历时只依赖“取下一个”,不关心内部结构。掌握 Python 的 迭代器协议(__iter__、__next__、StopIteration)。
要求
- 实现一个 MyList 类:内部用 list 存数据,有
add(item)方法。 - 实现 MyList 的
__iter__(),返回一个迭代器对象;该迭代器实现__iter__()和__next__(),按顺序逐个返回元素,没有下一个时抛出 StopIteration。 - 客户代码使用 for x in my_list 遍历,并打印每个元素;再用 iter() 和 next() 手动取两次,验证行为。
参考答案
class MyList:
def __init__(self):
self._data = []
def add(self, item):
self._data.append(item)
def __iter__(self):
return _MyListIterator(self._data)
class _MyListIterator:
def __init__(self, data: list):
self._data = data
self._index = 0
def __iter__(self):
return self
def __next__(self):
if self._index >= len(self._data):
raise StopIteration
val = self._data[self._index]
self._index += 1
return val
# 使用 for 遍历
my_list = MyList()
my_list.add("A")
my_list.add("B")
my_list.add("C")
for x in my_list:
print(x) # A, B, C
# 使用 iter() 和 next() 手动取
it = iter(my_list)
print(next(it)) # A
print(next(it)) # B
# 再取会抛 StopIteration(或再 next 两次后 try/except 验证)
验证要点
for x in my_list依次打印 A、B、C。iter(my_list)得到迭代器,next(it)两次得到 A、B;第三次next(it)应抛出 StopIteration(可在 try/except 中验证)。- 理解:MyList 不直接实现
__next__,而是由它返回的 _MyListIterator 实现;for 循环会先调my_list.__iter__()再反复调迭代器的__next__()。
练习二:同一集合提供逆序迭代器
目的
同一集合可以有不同的“遍历方式”;正序用 __iter__(for in),逆序用单独的方法返回逆序迭代器,体会“多种遍历 = 多种迭代器”。
要求
- 在练习一的基础上,为 MyList 增加一个方法 reverse_iterator(),返回一个按逆序遍历元素的迭代器(从最后一个到第一个)。
- 逆序迭代器也实现
__iter__和__next__,可被 for 使用。 - 客户代码:先 for 正序打印,再 for 逆序打印,验证顺序相反。
参考答案
class MyList:
def __init__(self):
self._data = []
def add(self, item):
self._data.append(item)
def __iter__(self):
return _MyListIterator(self._data)
def reverse_iterator(self):
return _ReverseIterator(self._data)
class _MyListIterator:
def __init__(self, data: list):
self._data = data
self._index = 0
def __iter__(self):
return self
def __next__(self):
if self._index >= len(self._data):
raise StopIteration
val = self._data[self._index]
self._index += 1
return val
class _ReverseIterator:
def __init__(self, data: list):
self._data = data
self._index = len(data) - 1
def __iter__(self):
return self
def __next__(self):
if self._index < 0:
raise StopIteration
val = self._data[self._index]
self._index -= 1
return val
# 使用
my_list = MyList()
my_list.add(1)
my_list.add(2)
my_list.add(3)
print("正序:", [x for x in my_list]) # 正序: [1, 2, 3]
print("逆序:", [x for x in my_list.reverse_iterator()]) # 逆序: [3, 2, 1]
验证要点
- 正序 for 得到 [1, 2, 3],逆序 for 得到 [3, 2, 1]。
- 集合内部结构(list)没有暴露给客户;客户只通过“迭代器”按顺序拿元素。
练习三:过滤迭代器(只遍历满足条件的元素)
目的
迭代器不仅可以“按顺序”取,还可以“按条件”取;实现一个 FilterIterator,接收一个可迭代对象和一个条件函数,只返回满足条件的元素,体会“多种遍历”中的“过滤遍历”。
要求
- 实现 FilterIterator:构造函数接收 data(list 或任意可迭代对象)和 condition(一个函数,接收元素返回 bool)。
- FilterIterator 实现
__iter__和__next__;在__next__中跳过不满足 condition 的元素,只返回满足条件的元素,没有更多满足条件的元素时抛出 StopIteration。 - 客户代码:对列表
[1, 2, 3, 4, 5, 6]使用 FilterIterator,条件为“偶数”,用 for 遍历应得到 2, 4, 6。
参考答案
class FilterIterator:
def __init__(self, data, condition):
"""
data: 可迭代对象(如 list)
condition: 函数 elem -> bool,只保留使 condition(elem) 为 True 的元素
"""
self._data = list(data)
self._condition = condition
self._index = 0
def __iter__(self):
return self
def __next__(self):
while self._index < len(self._data):
val = self._data[self._index]
self._index += 1
if self._condition(val):
return val
raise StopIteration
# 使用:只遍历偶数
data = [1, 2, 3, 4, 5, 6]
evens = [x for x in FilterIterator(data, lambda x: x % 2 == 0)]
print(evens) # [2, 4, 6]
验证要点
FilterIterator(data, lambda x: x % 2 == 0)用 for 遍历得到 [2, 4, 6]。- 可选:再写一个条件
lambda x: x > 3,验证得到 [4, 5, 6];说明同一份 data 可以通过不同迭代器得到不同“视图”。
三步汇总与自检
| 练习 | 重点 | 关键点 |
|---|---|---|
| 一 | 集合 + 迭代器协议 | __iter__ 返回迭代器,迭代器实现 __next__ 和 StopIteration;for 依赖这两者。 |
| 二 | 多种遍历 | 正序、逆序由不同迭代器类实现;集合提供不同方法返回不同迭代器。 |
| 三 | 过滤迭代器 | 迭代器内部跳过不满足条件的元素,只 yield/return 满足条件的;不修改原集合。 |
自检问题
-
for x in obj 时,Python 会先调什么?没有下一个元素时迭代器应该怎么做?
会先调obj.__iter__()得到迭代器,再反复调迭代器的__next__();没有下一个时应抛出 StopIteration,for 自动结束。 -
为什么说“迭代器用完后一般不重置”?
每个迭代器维护自己的“当前位置”;要再遍历一遍应重新从集合取一个新的迭代器(如再执行一次__iter__()),而不是重置同一个迭代器。 -
过滤迭代器会不会修改原集合?
不会;它只是在遍历时跳过不满足条件的元素,原集合不变;若要“只保留偶数”的集合,需要新建列表或新集合。
做完以上三道练习,再对照《迭代器模式》文档中的示例,对迭代器模式会掌握得比较扎实。建议每道题先自己写一遍,再对照参考答案和验证要点检查。