迭代是数据处理的基石。扫描内存中放不下的数据集时,咱们要找到一种惰性获取数据
项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)。python
看个例子正则表达式
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __getitem__(self, index): return self.words[index] def __len__(self): return len(self.words) def __repr__(self): return 'Sentence(%s)' % reprlib.repr(self.text) s = Sentence('"The time has come," the Walrus said,') print(s) for world in s: print(world)
解释器须要迭代对象 x 时,会自动调用 iter(x)。dom
内置的 iter 函数有如下做用。ssh
Python 从可迭代的对象中获取迭代器函数
next
返回下一个可用的元素,若是没有元素了,抛出 StopIteration 异常。
iter
返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。spa
abc.Iterator 类code
from abc import abstractmethod class Iterator(Iterable): __slots__ = () @abstractmethod def __next__(self): 'Return the next item from the iterator. When exhausted, raise StopIteration' raise StopIteration def __iter__(self): return self @classmethod def __subclasshook__(cls, C): if cls is Iterator: if (any("__next__" in B.__dict__ for B in C.__mro__) and any("__iter__" in B.__dict__ for B in C.__mro__)): return True return NotImplemented
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __getitem__(self, index): return self.words[index] def __len__(self): return len(self.words) def __repr__(self): return 'Sentence(%s)' % reprlib.repr(self.text) # s = Sentence('"The time has come," the Walrus said,') # print(s) # # for world in s: # print(world) ## it就是构造后的迭代器 ## iter(s3) 就是构建迭代器的可迭代对象。 s3 = Sentence('Pig and Pepper') it = iter(s3) print(next(it)) print(next(it)) print(next(it)) # 报错 StopIteration # print(next(it)) print(list(it)) print(list(iter(s3))) print(list(iter(s3)))
由于迭代器只需 next 和 iter 两个方法,因此除了调用 next() 方法,以及捕获 StopIteration 异常以外,没有办法检查是否还有遗留的元素。
此外,也没有办法“还原”迭代器。对象
若是想再次迭代,那就要调用 iter(...),传入以前构建迭代器的可迭代对象。blog
传入迭代器自己没用,由于前面说过 Iterator.__iter__ 方法的实现方式是
返回实例自己,因此传入迭代器没法还原已经耗尽的迭代器。索引
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __repr__(self): return 'Sentence(%s)' % reprlib.repr(self.text) def __iter__(self): return SentenceIterator(self.words) class SentenceIterator: def __init__(self, words): self.words = words self.index = 0 def __next__(self): try: word = self.words[self.index] except IndexError: raise StopIteration() self.index += 1 return word def __iter__(self): return self s = Sentence('"The time has come," the Walrus said,') for word in s: print(word)
❶ 与前一版相比,这里只多了一个 iter 方法。这一版没有 getitem 方法,为
的是明确代表这个类能够迭代,由于实现了 iter 方法。
❷ 根据可迭代协议,__iter__ 方法实例化并返回一个迭代器。
Sentence 类中,__iter__ 方法调用 SentenceIterator 类的构造方法建立一个迭代器并将其返回。
构建可迭代的对象和迭代器时常常会出现错误,缘由是混淆了两者。
要知道,可迭代的对象有个 iter 方法,每次都实例化一个新的迭代器;
而迭代器要实现 next 方法,返回单个元素,此外还要实现 iter 方法,返回迭代器自己。
所以,迭代器能够迭代,可是可迭代的对象不是迭代器。
除了 iter 方法以外,你可能还想在 Sentence 类中实现 next 方法,让
Sentence 实例既是可迭代的对象,也是自身的迭代器。
但是,这种想法很是糟糕。根据有大量 Python 代码审查经验的 Alex Martelli 所说,这也是常见的反模式。
生成器函数
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) def __repr__(self): return 'Sentence(%s)' % reprlib.repr(self.text) def __iter__(self): for word in self.words: yield word return s = Sentence('"The time has come," the Walrus said,') for word in s: print(word)
❸ 这个 return 语句不是必要的;这个函数能够直接“落空”,自动返回。无论有没有
return 语句,生成器函数都不会抛出 StopIteration 异常,而是在生成彻底部值以后会直接退出。
❹ 不用再单独定义一个迭代器类!
迭代器实际上是生成器对象,每次调用 iter 方法都会自动建立,由于这里的 iter 方法是生成器函数。
def gen_123(): # ➊ yield 1 # ➋ yield 2 yield 3 print(gen_123) print(gen_123()) for i in gen_123(): # ➎ print(i) g = gen_123() # ➏ print(next(g)) print(next(g)) print(next(g)) ## 报错 StopIteration # print(next(g))
❸ 仔细看,gen_123 是函数对象。
❻ 为了仔细检查,咱们把生成器对象赋值给 g。
❽ 生成器函数的定义体执行完毕后,生成器对象会抛出 StopIteration 异常。
把生成器传给next(...) 函数时,生成器函数会向前,执行函数定义体中的下一个 yield 语句,返回产出的值,并在函数定义体的当前位置暂停。
最终,函数的定义体返回时,外层的生成器对象会抛出 StopIteration 异常——这一点与迭代器协议一致。
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = text def __repr__(self): return 'Sentence(%s)' % reprlib.repr(self.text) def __iter__(self): # 返回一个迭代器 for match in RE_WORD.finditer(self.text): yield match.group()
re.finditer 函数是 re.findall 函数的惰性版本,返回的不是列表,而是一个生成
器,按需生成 re.MatchObject 实例。
若是有不少匹配,re.finditer 函数能节省大量内存。
咱们要使用这个函数让第 4 版 Sentence 类变得懒惰,即只在须要时才生成下一个单词。
❶ 再也不须要 words 列表。
❷ finditer 函数构建一个迭代器,包含 self.text 中匹配 RE_WORD 的单词,产出
MatchObject 实例。
❸ match.group() 方法从 MatchObject 实例中提取匹配正则表达式的具体文本。
生成器表达式能够理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。
def gen_AB(): # ➊ print('start') yield 'A' print('continue') yield 'B' print('end.') res1 = [x * 3 for x in gen_AB()] for i in res1: # ➌ print('-->', i) res2 = (x * 3 for x in gen_AB()) # ➍ print(res2) # ➎ for i in res2: # ➏ print('-->', i)
❷ 列表推导迫切地迭代 gen_AB() 函数生成的生成器对象产出的元素:'A' 和 'B'。注意,下面的输出是 start、continue 和 end.。
❸ 这个 for 循环迭代列表推导生成的 res1 列表。
❹ 把生成器表达式返回的值赋值给 res2。只需调用 gen_AB() 函数,虽然调用时会返回
一个生成器,可是这里并不使用。
❺ res2 是一个生成器对象。
❻ 只有 for 循环迭代 res2 时,gen_AB 函数的定义体才会真正执行。
for 循环每次迭代时会隐式调用 next(res2),前进到 gen_AB 函数中的下一个yield 语句。
注意,gen_AB 函数的输出与 for 循环中 print 函数的输出夹杂在一块儿。
根据个人经验,选择使用哪一种句法很容易判断:若是生成器表达式要分红多行写,我倾向
于定义生成器函数,以便提升可读性。此外,生成器函数有名称,所以能够重用。
class ArithmeticProgression: def __init__(self, begin, step, end=None): # ➊ self.begin = begin self.step = step self.end = end # None -> 无穷数列 def __iter__(self): result = type(self.begin + self.step)(self.begin) # ➋ forever = self.end is None # ➌ index = 0 while forever or result < self.end: # ➍ yield result # ➎ index += 1 result = self.begin + self.step * index # ➏
❸ 为了提升可读性,咱们建立了 forever 变量,若是 self.end 属性的值是 None那么forever 的值是 True,所以生成的是无穷数列。
❹ 这个循环要么一直执行下去,要么当 result 大于或等于 self.end 时结束。若是循环退出了,那么这个函数也随之退出。
在示例 14-11 中的最后一行,我没有直接使用 self.step 不断地增长 result,
而是选择使用 index 变量,把 self.begin 与 self.step 和 index 的乘积加,计算 result 的各个值,
以此下降处理浮点数时累积效应致错的风险。
但是,iter 函数还有一个不为人知的用法:传入两个参数,使用常规的函数或任何可调
用的对象建立迭代器。
这样使用时,第一个参数必须是可调用的对象,用于不断调用(没有参数),产出各个值;
第二个值是哨符,这是个标记值,当可调用的对象返回这个值时,触发迭代器抛出 StopIteration 异常,而不产出哨符。
iter 函数掷骰子,直到掷出 1 点为止
from random import randint def d6(): return randint(1, 6) d6_iter = iter(d6, 1) print(d6_iter) for roll in d6_iter: print(roll)