Python自定义迭代器:从入门到精通

目录

一、迭代器协议的核心要求

二、第一个自定义迭代器:生成指定范围的偶数

使用这个迭代器

三、深入理解:迭代器的工作原理

[1. 为什么__iter__()必须返回 self?](#1. 为什么__iter__()必须返回 self?)

[2. 迭代器的 "一次性" 特性](#2. 迭代器的 "一次性" 特性)

[3. 如何实现可多次遍历的 "可迭代对象"?](#3. 如何实现可多次遍历的 "可迭代对象"?)

四、实用自定义迭代器示例

[示例 1:逆序遍历列表的迭代器](#示例 1:逆序遍历列表的迭代器)

[示例 2:无限斐波那契数列迭代器](#示例 2:无限斐波那契数列迭代器)

[示例 3:分批处理数据的迭代器](#示例 3:分批处理数据的迭代器)

五、更简单的方式:生成器(Generator)

生成器的优势

生成器表达式

[六、手动实现 vs 生成器:如何选择?](#六、手动实现 vs 生成器:如何选择?)

七、自定义迭代器的常见坑

八、总结:自定义迭代器的步骤


自定义迭代器的核心是严格遵守 Python 迭代器协议 。只要一个类实现了__iter__()__next__()这两个特殊方法,它的实例就是一个合法的迭代器。

一、迭代器协议的核心要求

任何自定义迭代器都必须满足以下两个条件:

  1. __iter__(self) :必须返回迭代器对象本身(return self)。这是为了让迭代器可以直接用在for循环等接受可迭代对象的地方。
  2. __next__(self)
    • 返回序列中的下一个元素
    • 更新内部状态,指向下一个元素
    • 当没有更多元素时,必须抛出StopIteration异常

二、第一个自定义迭代器:生成指定范围的偶数

我们从最简单的例子开始,实现一个能生成从startend之间所有偶数的迭代器:

python 复制代码
class EvenIterator:
    def __init__(self, start, end):
        # 初始化迭代器的状态
        self.current = start if start % 2 == 0 else start + 1  # 确保从偶数开始
        self.end = end

    def __iter__(self):
        # 迭代器必须返回自己
        return self

    def __next__(self):
        if self.current > self.end:
            # 没有更多元素,抛出异常终止迭代
            raise StopIteration
        # 保存当前值
        result = self.current
        # 更新状态,指向下一个偶数
        self.current += 2
        # 返回当前值
        return result

使用这个迭代器

python 复制代码
# 创建迭代器实例
even_iter = EvenIterator(1, 10)

# 1. 使用next()手动遍历
print(next(even_iter))  # 输出:2
print(next(even_iter))  # 输出:4
print(next(even_iter))  # 输出:6

# 2. 直接用for循环遍历(自动处理next()和StopIteration)
for num in EvenIterator(1, 10):
    # end控制每个输出的数用空格隔开,默认是换行'\n'
    print(num, end=' ')  # 输出:2 4 6 8 10

三、深入理解:迭代器的工作原理

1. 为什么__iter__()必须返回 self?

因为 Python 中所有接受 "可迭代对象" 的地方(for循环、list()sum()等),都会先调用iter(对象)获取迭代器。

如果__iter__()返回的不是 self,那么for循环拿到的就是另一个对象,而不是我们的迭代器本身,迭代逻辑就会失效。

2. 迭代器的 "一次性" 特性

迭代器的状态是不可逆的,一旦遍历到末尾,就无法再从头开始:

python 复制代码
even_iter = EvenIterator(1, 10)

# 第一次遍历:正常输出
print(list(even_iter))  # 输出:[2, 4, 6, 8, 10]

# 第二次遍历:空列表!
print(list(even_iter))  # 输出:[]

这是因为第一次遍历后,self.current已经变成了 12,再调用next()会直接抛出StopIteration

3. 如何实现可多次遍历的 "可迭代对象"?

如果需要多次遍历,应该将可迭代对象迭代器分开实现:

  • 可迭代对象:实现__iter__(),每次返回一个新的迭代器实例
  • 迭代器:实现__iter__()__next__()
python 复制代码
# 可迭代对象(可以多次遍历)
class EvenNumbers:
    def __init__(self, start, end):
        self.start = start
        self.end = end

    def __iter__(self):
        # 每次调用iter()都返回一个新的迭代器
        return EvenIterator(self.start, self.end)

# 现在可以多次遍历了
evens = EvenNumbers(1, 10)
print(list(evens))  # 输出:[2, 4, 6, 8, 10]
print(list(evens))  # 输出:[2, 4, 6, 8, 10]

四、实用自定义迭代器示例

示例 1:逆序遍历列表的迭代器

python 复制代码
class ReverseIterator:
    def __init__(self, data):
        self.data = data
        self.index = len(data)  # 从最后一个元素的下一个位置开始

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index -= 1
        return self.data[self.index]

# 使用
for char in ReverseIterator('hello'):
    print(char, end=' ')  # 输出:o l l e h

示例 2:无限斐波那契数列迭代器

这是迭代器最强大的应用之一 ------ 生成无限序列

python 复制代码
class FibonacciIterator:
    def __init__(self):
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        result = self.a
        self.a, self.b = self.b, self.a + self.b
        return result

# 使用:按需生成,永远不会结束
fib = FibonacciIterator()
for _ in range(15):
    print(next(fib), end=' ')  # 输出:0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

示例 3:分批处理数据的迭代器

python 复制代码
class BatchIterator:
    def __init__(self, data, batch_size):
        self.data = data
        self.batch_size = batch_size
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration
        # 计算当前批次的结束位置
        end = self.index + self.batch_size
        # 切片获取当前批次
        batch = self.data[self.index:end]
        # 更新索引
        self.index = end
        return batch

# 使用
data = list(range(100))
for batch in BatchIterator(data, 10):
    print(f"处理批次:{batch}")
    # 在这里处理每一批数据

五、更简单的方式:生成器(Generator)

手动实现__iter__()__next__()虽然清晰,但比较繁琐。Python 提供了生成器,可以用更简洁的语法创建迭代器。

生成器使用yield关键字,自动实现了迭代器协议:

python 复制代码
# 生成器函数:等价于上面的EvenIterator
def even_generator(start, end):
    current = start if start % 2 == 0 else start + 1
    while current <= end:
        yield current  # 暂停执行,返回当前值
        current += 2

# 使用方式和迭代器完全一样
for num in even_generator(1, 10):
    print(num, end=' ')  # 输出:2 4 6 8 10

生成器的优势

  1. 代码更简洁:不需要定义类和两个特殊方法
  2. 自动管理状态:Python 会自动保存函数的执行状态
  3. 可读性更高:逻辑更直观,更容易理解

生成器表达式

对于简单的迭代逻辑,还可以使用生成器表达式,这是创建迭代器的最简洁方式:

python 复制代码
# 生成器表达式:等价于even_generator(1, 10)
even_gen = (x for x in range(1, 11) if x % 2 == 0)

print(list(even_gen))  # 输出:[2, 4, 6, 8, 10]

六、手动实现 vs 生成器:如何选择?

实现方式 优点 缺点 适用场景
手动实现迭代器 完全控制迭代逻辑,灵活性最高 代码繁琐,容易出错 复杂的迭代逻辑、需要维护复杂状态
生成器函数 代码简洁,可读性高,自动管理状态 灵活性稍低 大多数场景,尤其是中等复杂度的迭代逻辑
生成器表达式 最简洁,一行代码搞定 只能实现简单逻辑 简单的转换、过滤操作

七、自定义迭代器的常见坑

  1. 忘记在__next__()中抛出StopIteration:会导致无限循环
  2. __iter__()没有返回 self:迭代器无法用在 for 循环中
  3. 迭代器状态没有正确更新:会导致重复返回同一个元素或跳过元素
  4. 试图多次遍历同一个迭代器:第二次遍历会得到空结果

八、总结:自定义迭代器的步骤

  1. 定义一个类
  2. __init__()方法中初始化迭代器的状态
  3. 实现__iter__()方法,返回 self
  4. 实现__next__()方法:
    • 检查是否还有更多元素
    • 如果没有,抛出StopIteration
    • 如果有,计算并返回当前元素
    • 更新内部状态,指向下一个元素

总而言之:自定义迭代器是 Python 迭代机制的底层核心,通过遵守迭代器协议,我们可以实现高效、灵活的数据遍历。生成器是简化版的自定义迭代器,适用于大多数场景;而手动实现类则提供了最高的灵活性,满足复杂的迭代需求。

相关推荐
不会C语言的男孩9 小时前
C++ Primer Plus 第3章:处理数据
开发语言·c++
叶帆9 小时前
【YFIOs】用C#开发硬件之GPIO操作
开发语言·c#
在繁华处9 小时前
Hermes Agent 完全使用指南:从安装到多平台部署的全流程教程
python·开源·飞书
Starry-sky(jing)10 小时前
Hermes Agent 接入 Qwen3.7-Max 报 401?OpenCode Go 模型路由源码级排查与修复
开发语言·人工智能·chrome·golang
likerhood10 小时前
Java 集合框架入门:List、Set、Queue 与 Map
java·开发语言·list
暴躁小师兄数据学院10 小时前
【AI大模型应用开发工程师特训笔记】第04讲(第五章):条件判断与流程控制
大数据·人工智能·python·学习
郝学胜-神的一滴10 小时前
系统设计 013:高并发系统缓存:从原理到实践全解析
java·开发语言·python·缓存·系统架构·php·软件构建
学困昇10 小时前
Linux 信号机制详解:从 Ctrl+C 到 SIGCHLD,一文理解进程信号
linux·c语言·开发语言·人工智能·面试
半壶清水10 小时前
用 Python 和 OpenCV 提取书法作品中的每一个单字
python·opencv·计算机视觉