流畅的python14 章可迭代的对象迭代器 和生成器
Posted a3384451
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了流畅的python14 章可迭代的对象迭代器 和生成器相关的知识,希望对你有一定的参考价值。
可迭代的对象、迭代器和生成器
迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)。
迭代器用于从集合中取出元素;而生成器用于“凭空”生成元素。通过斐波纳契数列能很好地说明二者之间的区别:斐波纳契数
列中的数有无穷个,在一个集合里放不下。不过要知道,在 Python社区中,大多数时候都把迭代器和生成器视作同一概念。
Sentence类
单词序列
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) ?
?re.findall 函数返回一个字符串列表,里面的元素是正则表达式的
全部非重叠匹配。
? self.words 中保存的是 .findall 函数返回的结果,因此直接返回
指定索引位上的单词。
? 为了完善序列协议,我们实现了 __len__ 方法;不过,为了让对象
可以迭代,没必要实现这个方法。
? reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表
示形式。
示例 14-2 测试 Sentence 实例能否迭代
>>> s = Sentence(‘"The time has come," the Walrus said,‘) # ? >>> s Sentence(‘"The time ha... Walrus said,‘) # ? >>> for word in s: # ? ... print(word) The time has come the Walrus said >>> list(s) # ? [‘The‘, ‘time‘, ‘has‘, ‘come‘, ‘the‘, ‘Walrus‘, ‘said‘]
? 传入一个字符串,创建一个 Sentence 实例。
? 注意,__repr__ 方法的输出中包含 reprlib.repr 方法生成的
...。
? Sentence 实例可以迭代,稍后说明原因。
? 因为可以迭代,所以 Sentence 对象可以用于构建列表和其他可迭代
的类型。
序列可以迭代的原因:iter函数
解释器需要迭代对象 x 时,会自动调用 iter(x)。内置的 iter 函数有以下作用。
- (1) 检查对象是否实现了 __iter__ 方法,如果实现了就调用它,获取一个迭代器。
- (2) 如果没有实现 __iter__ 方法,但是实现了 __getitem__ 方法,Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素。
- (3) 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C objectis not iterable”(C 对象不可迭代),其中 C 是目标对象所属的类。任何 Python 序列都可迭代的原因是,它们都实现了 __getitem__ 方法。其实,标准的序列也都实现了 __iter__ 方法,因此你也应该这么做。之所以对 __getitem__ 方法做特殊处理,是为了向后兼容,而未来可能不会再这么做
可迭代的对象与迭代器的对比
可迭代的对象
使用 iter 内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的 __iter__ 方法,那么对象就是可迭代的。序列都可以迭代;实现了 __getitem__ 方法,而且其参数是从零开始的索引,这种
对象也可以迭代。
我们要明确可迭代的对象和迭代器之间的关系:Python 从可迭代的对象中获取迭代器。
下面是一个简单的 for 循环,迭代一个字符串。这里,字符串 ‘ABC‘是可迭代的对象。背后是有迭代器的,只不过我们看不到:
>>> s = ‘ABC‘ >>> for char in s: ... print(char) ... A B C ###如果没有 for 语句,不得不使用 while 循环模拟,要像下面这样写: >>> s = ‘ABC‘ >>> it = iter(s) # ? >>> while True: ... try: ... print(next(it)) # ? ... except StopIteration: # ? ... del it # ? ... break # ? ... A B C
? 使用可迭代的对象构建迭代器 it。
? 不断在迭代器上调用 next 函数,获取下一个字符。
? 如果没有字符了,迭代器会抛出 StopIteration 异常。
? 释放对 it 的引用,即废弃迭代器对象。
? 退出循环。
StopIteration 异常表明迭代器到头了。Python 语言内部会处理 for循环和其他迭代上下文(如列表推导、元组拆包,等等)中的StopIteration 异常。
标准的迭代器接口有两个方法。
__next__
返回下一个可用的元素,如果没有元素了,抛出 StopIteration异常。
__iter__
返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。
迭代器
迭代器是这样的对象:实现了无参数的 __next__ 方法,返回序列中的下一个元素;如果没有元素了,那么抛出 StopIteration 异常。Python 中的迭代器还实现了 __iter__ 方法,因此迭代器也可以迭代。
典型的迭代器
使用迭代器模式实现 Sentence 类
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): #与前一版相比,这里只多了一个 __iter__ 方法。这一版没有 #__getitem__ 方法,为的是明确表明这个类可以迭代,因为实现了 #__iter__ 方法。 return SentenceIterator(self.words) #根据可迭代协议,__iter__ 方法实例化并返回一个迭代器。 class SentenceIterator: def __init__(self, words): self.words = words #SentenceIterator 实例引用单词列表。 self.index = 0#self.index 用于确定下一个要获取的单词。 def __next__(self): try: word = self.words[self.index] #获取 self.index 索引位上的单词。 except IndexError: raise StopIteration() #如果 self.index 索引位上没有单词,那么抛出 StopIteration 异 常。 self.index += 1 return word def __iter__(self): #实现 self.__iter__ 方法。 return self
生成器函数
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
生成器函数的工作原理
只要 Python 函数的定义体中有 yield 关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。
惰性实现
目前实现的几版 Sentence 类都不具有惰性,因为 __init__ 方法急迫地构建好了文本中的单词列表,然后将其绑定到 self.words 属性上。
这样就得处理整个文本,列表使用的内存量可能与文本本身一样多(或许更多,这取决于文本中有多少非单词字符)。如果只需迭代前几个单词,大多数工作都是白费力气。
只要使用的是 Python 3,思索着做某件事有没有懒惰的方式,答案通常都是肯定的。
re.finditer 函数是 re.findall 函数的惰性版本,返回的不是列表,而是一个生成器,按需生成 re.MatchObject 实例。如果有很多匹配,re.finditer 函数能节省大量内存。我们要使用这个函数让第 4版 Sentence 类变得懒惰,即只在需要时才生成下一个单词。代码如示
例 14-7 所示。示例 14-7 sentence_gen2.py: 在生成器函数中调用 re.finditer生成器函数,实现 Sentence 类
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()
? 不再需要 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()] # ? start continue end. >>> for i in res1: # ? ... print(‘-->‘, i) ... --> AAA --> BBB >>> res2 = (x*3 for x in gen_AB()) # ? >>> res2 # ? <generator object <genexpr> at 0x10063c240> >>> for i in res2: # ? ... print(‘-->‘, i) ... start --> AAA continue --> BBB end.
? gen_AB 函数与示例 14-6 中的一样。
? 列表推导迫切地迭代 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 函数的输出夹杂在一起。
使用生成器表达式实现 Sentence类
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): return (match.group() for match in RE_WORD.finditer(self.text))
这里不是生成器函数了(没有 yield),而是使用生成器表达式构建生成器,然后将其返回。不过,最终的效果一样:调用 __iter__ 方法会得到一个生成器对象。
生成器表达式是语法糖:完全可以替换成生成器函数,不过有时使用生成器表达式更便利。
标准库中的生成器函数
用于过滤的生成器函数
模块 | 函数 | 说明 |
itertools |
compress(it, |
并行处理两个可迭代的对象;如果 selector_it |
itertools |
dropwhile(predicate, |
处理 it ,跳过 predicate 的计算结果为真值的元 |
内置 | filter(predicate, it) |
把 it 中的各个元素传给 predicate ,如果 |
itertools |
filterfalse(predicate, |
与 filter 函数的作用类似,不过 predicate 的 |
itertools |
islice(it, stop) 或 |
产出 it 的切片,作用类似于 s[:stop] 或 |
itertools |
takewhile(predicate, |
predicate 返回真值时产出对应的元素,然后立 |