当前位置:首页>python>【Python】:迭代器与可迭代对象,背后的设计哲学

【Python】:迭代器与可迭代对象,背后的设计哲学

  • 2026-09-04 17:47:34
【Python】:迭代器与可迭代对象,背后的设计哲学
迭代机制:一套统一访问数据的协议
在Python中,迭代机制是基于两个魔法方法定义的,分别是__iter__()、__next__()
python里,迭代机制的构成是由两个角色来实现的:迭代器和可迭代对象
可迭代对象的实现:实现了__iter__方法,其返回值是:全新迭代器实例
迭代器的实现:实现了__iter__方法和__next__方法。
    • __iter__方法返回的是自身,即return self;
    • __next__方法,取出下一个元素
用一句话总结两个方法的职责:
  • __iter__:解决从谁那里获取元素
  • __next__:取出下一个元素,维护遍历位置
代码示例:
# 迭代器:负责保存遍历状态,实现 __iter__ + __next__class MyIterator:    def __init__(self, data):        self.data = data        self.index = 0  # 遍历游标(状态存在这里)    def __iter__(self):        # 协议规定:迭代器的 __iter__ 返回自身        return self    def __next__(self):        if self.index >= len(self.data):            raise StopIteration        val = self.data[self.index]        self.index += 1        return val# 可迭代对象:只负责生产迭代器,不保存遍历位置class MyIterable:    def __init__(self, data):        self.data = data    def __iter__(self):        # 每次调用都返回全新迭代器实例        return MyIterator(self.data)# ========== 测试 ==========obj = MyIterable([10, 20, 30])it1 = iter(obj)it2 = iter(obj)print(next(it1))  # 10print(next(it1))  # 20print(next(it2))  # 10  两个迭代器互相独立!print("第一次遍历:")for x in obj:    print(x)print("第二次遍历:")for x in obj:    print(x)# 可以重复遍历

Python 为什么要拆成「可迭代对象 Iterable」+「迭代器 Iterator」两套角色?
迭代器与可迭代对象,两者总是让人混淆,要了解一个人为的事物,最好还是从设计者的角度去思考。
Python为什么要设计出两个角色?
先说结论:实现【同一份数据源,支持同时/多次独立遍历】
先考虑不拆分出迭代器与可迭代对象的情景
有一个列表[1, 2, 3],是迭代器,也是可迭代对象
那么其内部就得有一个游标pos,来记录其遍历的位置。
一种场景是:我遍历了一轮列表,现在我还想进行第二轮的遍历。
结果是:不好意思,第一轮结束,游标记录的位置是末尾,第二次遍历时,什么也都取不出来。
第二种场景是:两个循环遍历同一个列表
lst = [1, 2, 3]it1 = iter(lst)it2 = iter(lst)# it1、it2 是同一个对象,共享同一个posnext(it1) # 1next(it2) # 2  顺序完全错乱!
这就是致命缺陷:数据源本身绑定遍历状态。
一份数据只能有一条遍历进度,无法并行、无法重新从头遍历。
若是拆分这个方案呢?
职责分开,分离 = 容器(数据)与遍历状态解耦
1. 可迭代对象(Iterable):只保存数据,不保存遍历位置
只实现__iter__(),作用是迭代器工厂。
每次调用iter(),生成一个全新迭代器。
2. 迭代器(Iterator):只保存遍历进度,不持有原始数据
实现__iter__() + __next__(),记录:遍历到第几个,计算规则
拆分的结果就是:多个迭代器互相独立,各自维护自己的进度,互不干扰
nums = [1,2,3]it1 = iter(nums)it2 = iter(nums)next(it1) #1next(it2) #1
完美解决了,不拆分情景下的问题。

迭代机制的价值
惰性求值:需要元素时才计算,不会一次性把全部数据载入内存

对比:

  1. [x**2 for x in range(N)] 列表推导:立即求值,全部存入内存
2. (x**2 for x in range(N)) 生成器表达式:惰性求值,仅保存计算规则与当前位置
总结:对于超大文件/海量流式数据的情景,文件对象本身就是迭代器,可以逐行读取,无需一次性加载整个文件。

生成器:迭代器的快速实现版
虽然迭代机制逻辑清晰,但是写代码的实现要用类class和魔法方法来实现。这在一些场景,太过于笨重了
所以,python又提供了两种极简的方式来创建迭代器:生成器函数与生成器表达式
1. 生成器函数:函数内部出现yield,就是生成器函数
  • 调用函数不会执行函数体,仅仅创建生成器对象;
  • 调用next(),才开始运行代码,遇到yield暂停,并产出数据;
  • 再次next(),从上次暂停位置继续往下执行;
  • 代码执行完毕,自动抛出StopIteration
2. 生成器表达式:(表达式 for 变量 in 可迭代对象 if 条件)
生成器对象本身就是迭代器
from collections.abc import Iterator, Iterabledef g():    yield 1gen = g()print(isinstance(gen, Iterator))  # Trueprint(isinstance(gen, Iterable))  # True
意味着:gen.__iter__()永远返回自身 return self
生成器既然是迭代器,就只能遍历一次,一次性消费,不能从头遍历
若想多次遍历,就重新调用生成器函数
延申:在python 2.5中,生成器有新的能力,不仅可以通过yield向外产出,还可以通过send()从外部接收值。而这种双向通信的能力,使其成为了协程的基础。直到python 3.5后,python又单独拿出一套语法,把「协程」和「迭代生成器」彻底分开。

最新文章

随机文章