流畅的python,Fluent Python 第十四章笔记 (可迭代的对象、迭代器和生成器)
迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据的方式,既按需一次获取一个数据项。这就是迭代器模式。
所有的生成器都石迭代器,因为生成器完全实现了迭代器的接口。不过根据《设计模式:可复用面向对象软件的基础》一书的定义,迭代器用于从集合中取出元素;而生成器用于"凭空"生成元素。
通过斐波那契数列能很好的说明二者之间的区别:斐波那契数列中的数有无穷个,在一个集合里放不下。不过要知道,再Python社区中,大多数时候把迭代器和生成器视为同一个概念。
14.1Sentence类第1版:单词排序
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: # 定义成序列的协议,有__getitem__与__len__ def __init__(self, text): self.text = text self.word = RE_WORD.findall(text) def __getitem__(self, item): return self.word[item] def __len__(self): return len(self.word) def __repr__(self): return f'{type(self).__name__}({reprlib.repr(self.text)})'
运行结果:
In [5]: s = Sentence('"The time has come," the Walrus said') In [8]: s Out[8]: Sentence('"The time ha...e Walrus said') In [9]: for word in s: ...: print(word) ...: The time has come the Walrus said In [10]: list(s) Out[10]: ['The', 'time', 'has', 'come', 'the', 'Walrus', 'said'] In [11]:
序列可以迭代的原因:iter函数
解释器需要迭代对象x时,会自动调用iter()
内置的iter函数有以下作用:
1、检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器
2、如果没有实现__iter__方法,但是实现了__getitem__方法,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素
3、如果尝试失败,Python抛出TypeError异常。
前面讲过,鸭子类型(duck typing)的极端形式:只要实现特殊的__iter__方法,或者实现__getitem__方法且__getitem__方法的参数是从0开始的整数(int),就可以认为是可迭代的。
abc.Iterable实现了__subclasshook__方法,可以来测试是不是可迭代对象。但不是很准确,因为如果就像前面定义的Sentence没有__iter__方法,它就认为该对象不属于可迭代对象。
因为用iter()函数测试,只要能被iter()通过的,都是可迭代对象,可以用try,except来测试,其实用list来测试也可以。
In [13]: from collections.abc import Iterable In [14]: isinstance('',Iterable) Out[14]: True In [15]: isinstance(dict(),Iterable) Out[15]: True In [17]: isinstance(s,Iterable) Out[17]: False In [18]: iter(s) Out[18]: <iterator at 0x10ea46ed0>
14.2可迭代的对象与迭代器的对比
可迭代的对象,使用iter内置函数可以获取迭代器对象。如果对象实现了能返回迭代器的__iter__方法,那么该对象就是可迭代的。
简单来说,只要有了__irer__方式的对象就是可迭代对象。
序列是可以迭代,实现了__getotem__方法,而且其参数是从零开始的索引,这种对象也可以迭代。
In [32]: s = 'abc' In [33]: for i in s: ...: print(i) ...: a b c In [34]: it = iter(s) In [35]: while True: ...: try: ...: print(next(it)) ...: except StopIteration: ...: del it ...: break ...: a b c In [36]:
for循环的轨迹,后面用while循环显示出来,我记得我以前看过一本数,说for循环其实就while循环的一种语法糖。
StopIteration异常表明迭代器到头了。Python语言内部会处理for循环和其他迭代上下文(如列表推导、元祖拆包、等等)中的StopIteraton异常
在colletions.abc的Iterable与Iterator中,两个都有__subclasshoon__方法,其中Iterator是Iterable的子类。
在Iterator的__subclasshoon__方法,可以让你不用继承该类去测试对象是否为迭代器。
迭代器是这样的对象:实现了午餐书的__next__方法,返回序列中的下一个元素;如果没有元素了,那么爆出StopIteration异常。Python中的迭代器还实现了__iter__方法,因为迭代器也是可以迭代的。
14.3 Sentence第二版:典型的迭代器
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: # 定义成序列的协议,有__getitem__与__len__ def __init__(self, text): self.text = text self.word = RE_WORD.findall(text) def __len__(self): return len(self.word) def __repr__(self): return f'{type(self).__name__}({reprlib.repr(self.text)})' def __iter__(self): return SentenceIterator(self.word) class SentenceIterator: # 返回一个迭代器 def __init__(self, words): self.words = words self.count = 0 def __next__(self): try: word = self.words[self.count] except IndexError: raise StopIteration self.count += 1 return word def __iter__(self): return self
运行结果:
In [37]: s = Sentence('"The time has come," the Walrus said') In [39]: s Out[39]: Sentence('"The time ha...e Walrus said') In [40]: it = iter(s) In [42]: next(it) Out[42]: 'The' In [43]: next(it) Out[43]: 'time' In [44]: list(it) Out[44]: ['has', 'come', 'the', 'Walrus', 'said'] In [45]: it Out[45]: <__main__.SentenceIterator at 0x10e8cd8d0> In [46]: next(it) --------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-36-1416aa8893b4> in __next__(self) 31 try: ---> 32 word = self.words[self.count] 33 except IndexError: IndexError: list index out of range During handling of the above exception, another exception occurred: StopIteration Traceback (most recent call last) <ipython-input-46-bc1ab118995a> in <module> ----> 1 next(it) <ipython-input-36-1416aa8893b4> in __next__(self) 32 word = self.words[self.count] 33 except IndexError: ---> 34 raise StopIteration 35 self.count += 1 36 return word
这是一种解剖式的for循环的时候,__iter__在干什么。
其实每次获取迭代对象的内容,书面语(为了支持多种遍历),必须能从同一个可迭代的实现中获取多个独立的迭代器,而且各个迭代器要能维护自身的内部状态,因此这一模式正确的实现方法是,每次调用iter都能新建一个独立的迭代器。
可迭代的对象一定不能是自身的迭代器。也就是说,可迭代对象不许实现__iter__方法,但不能实现__next__方法。
14.4 Sentence类第3版:生成器函数
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: # 定义成序列的协议,有__getitem__与__len__ def __init__(self, text): self.text = text self.word = RE_WORD.findall(text) def __len__(self): return len(self.word) def __repr__(self): return f'{type(self).__name__}({reprlib.repr(self.text)})' def __iter__(self): # 生成器函数 for word in self.word: yield word return # 这个return可以不写,因为生成器在函数体执行完毕后自动抛出StopIteration
上面将__iter__变成了一个生成器函数,通过这个接口,每次取迭代获取参数,都将获得一个生成器,不用像前面那么复杂的去创建一个迭代器。
生成器函数的工作原理
只要Python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。
以前总是错误的认为yield就是return的变相版本,其实这样理解是非常不合理的。
生成器,从字面定义就是生产,产出了什么,是凭空多出来的。所以,每一次yield就是产出一个数据,产出以后当然还能继续产出,所以整个函数体并不会执行结束。
这是一种惰性的特征。
14.5sentence类第4版:惰性实现
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: # 定义成序列的协议,有__getitem__与__len__ def __init__(self, text): self.text = text def __len__(self): return len(self.word) def __repr__(self): return f'{type(self).__name__}({reprlib.repr(self.text)})' def __iter__(self): # 生成器函数 for match in RE_WORD.finditer(self.text): # 惰性查找,构建了一个迭代器。 yield match.groups() # 产出查找对象的内容
14.6 Sentence类第5版:生成器表达式
In [57]: def gen_AB(): ...: print('start') ...: yield 'A' ...: print('continue') ...: yield 'B' ...: print('end') ...: In [58]: res2 = (x*3 for x in gen_AB()) In [59]: next(res2) start Out[59]: 'AAA' In [60]: next(res2) continue Out[60]: 'BBB' In [61]: next(res2) end --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-61-45c410d5a3dc> in <module>
这是通过next调用生成器的执行结果。
In [62]: def res2_gen(ob): ...: for i in ob: ...: yield i*3 ...: In [63]: res3= res2_gen(gen_AB()) In [64]: next(res3) start Out[64]: 'AAA' In [65]: for i in res3: ...: print(i) ...: continue BBB end In [66]:
通过对比发现,生成器表达式就是一个简单的生成器函数的语法糖写法。
生成器表达式的一个参数,是生成器函数yield产出的东西。
如果说生成器函数为一个五脏俱全的生成器工厂,那生成器表达式就是一个简化版的生成器加工作坊。
这个也可以说明,一些简单的逻辑的生成器函数可以用生成器表达式完成。
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: # 定义成序列的协议,有__getitem__与__len__ def __init__(self, text): self.text = text def __len__(self): return len(self.word) def __repr__(self): return f'{type(self).__name__}({reprlib.repr(self.text)})' def __iter__(self): # 返回一个生成器 return (match.groups() for match in RE_WORD.finditer(self.text))
14.8另一个示例:等差数列生成器
class ArithmeticProgression: def __init__(self, begin, step, end=None): self.begin = begin self.step = step self.end = end def __iter__(self): result = type(self.begin + self.step)(self.begin) # 初始化第一个数字,按照step的格式要求 forever = self.end is None # 判断有没有最后截止 index = 0 while forever or result < self.end: # 如果forever成立就是无线取值,forever不成立,有限循环,最后的值<设置的end yield result index += 1 result = self.bigin + self.step * index # 选择这种方式累加,可以降低处理浮点数时候累积效应致错的风险
运行的结果:
[76]: ap = ArithmeticProgression(0,1,3) In [77]: list(ap) Out[77]: [0, 1, 2] In [78]: ap = ArithmeticProgression(0,1/3,3) In [79]: list(ap) Out[79]: [0.0, 0.3333333333333333, 0.6666666666666666, 1.0, 1.3333333333333333, 1.6666666666666665, 2.0, 2.333333333333333, 2.6666666666666665] In [80]: from fractions import Fraction In [81]: ap = ArithmeticProgression(0,Fraction(1,3),1) In [82]: list(ap) Out[82]: [Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)] In [83]: from decimal import Decimal In [85]: ap = ArithmeticProgression(0,Decimal('.1'),0.5) In [86]: list(ap) Out[86]: [Decimal('0'), Decimal('0.1'), Decimal('0.2'), Decimal('0.3'), Decimal('0.4')] In [87]:
前面是通过类的方式,还需要实例化,然后通过iter得到迭代器,书中还有一个更加好的,用生成器函数,直接用函数返回一个生成器。
def aritprog_gen(begin, step, end=None): result = type(begin+step)(begin) forever = end is None index = 0 while forever or result<end: yield result index += 1 result = begin + step * index
这个直接调用这个函数,list或者for循环它都可以拿到它的元素
In [88]: ap = aritprog_gen(0,Decimal('.1'),0.5) In [89]: list(ap) Out[89]: [Decimal('0'), Decimal('0.1'), Decimal('0.2'), Decimal('0.3'), Decimal('0.4')] In [90]:
运行的结果当然也是一样的。
使用itertoos模块生成等差数列
先介绍count
In [99]: c = count(1,.5) In [100]: next(c) Out[100]: 1 In [103]: next(c) Out[103]: 1.5 In [104]: next(c) Out[104]: 2.0 In [105]: next(c) Out[105]: 2.5
跟前面自己定义的步进器有点像,但收个数字格式不对。
还有一个takewhile
In [106]: from itertools import takewhile In [107]: takewhile? Init signature: takewhile(self, /, *args, **kwargs) Docstring: takewhile(predicate, iterable) --> takewhile object Return successive entries from an iterable as long as the predicate evaluates to true for each entry. Type: type Subclasses: In [108]:
从说明来看,前面放一个函数,只要函数返回是True的,那写元素可以用通过这个takewhile这个类输出。
如果一旦有一个错误的,那后面的可迭代对象在这个错误体后面(包括这个错误体)不能再生产出元素。
感觉说的很奇怪
In [109]: take = takewhile(lambda x:x !='l','hello') In [110]: next(take) Out[110]: 'h' In [111]: next(take) Out[111]: 'e' In [112]: next(take) --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-112-f33df8f3fdf8> in <module> ----> 1 next(take) StopIteration: In [113]: In [113]: next(take) --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-113-f33df8f3fdf8> in <module> ----> 1 next(take) StopIteration:
代码运行出来的结果就很好理解了。
后面的hello,当碰到条件为flase时,就是说输出l的时候,迭代器结束工作,返回StopIteration。
通过与前面的count结合,可以设置一个有限的输出步进迭代器。
In [114]: sc = takewhile(lambda x: x<3,count(1,0.3)) In [115]: list(sc) Out[115]: [1, 1.3, 1.6, 1.9000000000000001, 2.2, 2.5, 2.8] In [116]:
既然这样,我们就通过内置的一些模块,修改前面写的模块
from itertools import count, takewhile def aritprog_gen(begin, step, end=None): first = type(begin+step)(begin) ap_gen = count(first, step) # 默认是count if end is not None: # 如果 end不为空 ap_gen = takewhile(lambda x: x < end, ap_gen) # 通过takewilhe重新限制生成器,并覆盖ap_gen return ap_gen # 返回一个生成器
In [117]: ari = aritprog_gen(1,1/2,4) In [118]: list(ari) Out[118]: [1.0, 1.5, 2.0, 2.5, 3.0, 3.5] In [119]:
14.9标准库中的生成器函数。
独立开一篇新随笔:https://www.cnblogs.com/sidianok/p/12150599.html
14.10Python3.3中新出现的语句:yield from
In [284]: def chain(*iterable): ...: for it in iterable: ...: for i in it: ...: yield i ...: In [285]: s = 'abc' In [286]: t = range(3) In [287]: list(chain(s,t)) Out[287]: ['a', 'b', 'c', 0, 1, 2] In [288]: def chain(*iterable): ...: for it in iterable: ...: yield from it ...: ...: In [289]: list(chain(s,t)) Out[289]: ['a', 'b', 'c', 0, 1, 2] In [290]:
yield from i完全替代了内层的for循环,yield from还会创建通道,把内层生成器直接与外层生成器的客户端联系起来。
14.11 可迭代的归约函数。
接收一个可迭代的对象,然后返回单个结果,这些函数叫做"归约"函数、"合拢"函数、或"累加"函数。
all,any,max,min,functools.reduce,sum,
其中all,any函数会短路
14.12 深入分析iter函数
In [290]: iter? Docstring: iter(iterable) -> iterator iter(callable, sentinel) -> iterator Get an iterator from an object. In the first form, the argument must supply its own iterator, or be a sequence. In the second form, the callable is called until it returns the sentinel. Type: builtin_function_or_method
还可以前面传入一个函数,后面传入一个哨兵,每次执行next,产出函数的值,如果函数产出的值不等于哨兵,就产出。
In [291]: def demo(): ...: return random.randrange(5) ...: In [292]: it = iter(demo,3) In [293]: next(it) Out[293]: 0 In [294]: next(it) Out[294]: 1 In [295]: next(it) Out[295]: 1 In [296]: next(it) --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-296-bc1ab118995a> in <module> ----> 1 next(it) StopIteration:
In [303]: it = iter(demo,3) In [304]: for i in it: ...: print(i) ...: 2