32·模块七:面向对象编程高级

迭代器协议(__iter__, __next__)

oopiteratorsprotocol

第32课 - 迭代器协议(__iter__, __next__

学习目标

完成本课学习后,你将能够:

  1. 理解Python中"迭代器"与"可迭代对象"的区别。
  2. 掌握并实现迭代器协议,包括 __iter__()__next__() 两个魔法方法。
  3. 理解 for 循环的内部工作原理。
  4. 能够根据需求,为自定义数据类型创建自定义的迭代器。
  5. 知道如何正确抛出 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在后台做了以下事情:

  1. 调用 iter(some_iterable),这会调用 some_iterable.__iter__(),获取一个迭代器对象。
  2. 开始循环,不断调用迭代器的 next(iterator) 方法(即调用 __next__())。
  3. 将返回的值赋值给 item
  4. __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

常见错误

  1. 混淆可迭代对象和迭代器:忘记 __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。
    
  2. 忘记抛出StopIteration:当没有更多元素时,必须显式 raise StopIteration,否则会造成无限循环。

    # 错误示例
    def __next__(self):
        if self.current >= self.end:
            # 这里什么都不做,程序会陷入死循环或者返回None
            # 正确做法: raise StopIteration
            return None # 这是错误的!
        # ...
    
  3. 在迭代中修改集合:在 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功能无缝协作。
  • 理解迭代器协议是理解下一课生成器的基础。生成器是一种更简洁、高效的创建迭代器的方式。

练习编辑器

python
Loading...

继续学习

完成本课后,建议继续学习下一课「生成器(Generators)与 yield」 以巩固所学知识。