在Python中,迭代机制是基于两个魔法方法定义的,分别是__iter__()、__next__()python里,迭代机制的构成是由两个角色来实现的:迭代器和可迭代对象可迭代对象的实现:实现了__iter__方法,其返回值是:全新迭代器实例迭代器的实现:实现了__iter__方法和__next__方法。- __iter__方法返回的是自身,即return self;
# 迭代器:负责保存遍历状态,实现 __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」两套角色?迭代器与可迭代对象,两者总是让人混淆,要了解一个人为的事物,最好还是从设计者的角度去思考。先说结论:实现【同一份数据源,支持同时/多次独立遍历】有一个列表[1, 2, 3],是迭代器,也是可迭代对象那么其内部就得有一个游标pos,来记录其遍历的位置。一种场景是:我遍历了一轮列表,现在我还想进行第二轮的遍历。结果是:不好意思,第一轮结束,游标记录的位置是末尾,第二次遍历时,什么也都取不出来。lst = [1, 2, 3]it1 = iter(lst)it2 = iter(lst)# it1、it2 是同一个对象,共享同一个posnext(it1) # 1next(it2) # 2 顺序完全错乱!
一份数据只能有一条遍历进度,无法并行、无法重新从头遍历。1. 可迭代对象(Iterable):只保存数据,不保存遍历位置2. 迭代器(Iterator):只保存遍历进度,不持有原始数据实现__iter__() + __next__(),记录:遍历到第几个,计算规则拆分的结果就是:多个迭代器互相独立,各自维护自己的进度,互不干扰nums = [1,2,3]it1 = iter(nums)it2 = iter(nums)next(it1) #1next(it2) #1
惰性求值:需要元素时才计算,不会一次性把全部数据载入内存对比:
[x**2 for x in range(N)] 列表推导:立即求值,全部存入内存
2. (x**2 for x in range(N)) 生成器表达式:惰性求值,仅保存计算规则与当前位置总结:对于超大文件/海量流式数据的情景,文件对象本身就是迭代器,可以逐行读取,无需一次性加载整个文件。
虽然迭代机制逻辑清晰,但是写代码的实现要用类class和魔法方法来实现。这在一些场景,太过于笨重了所以,python又提供了两种极简的方式来创建迭代器:生成器函数与生成器表达式1. 生成器函数:函数内部出现yield,就是生成器函数- 调用next(),才开始运行代码,遇到yield暂停,并产出数据;
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又单独拿出一套语法,把「协程」和「迭代生成器」彻底分开。