流畅的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
浙公网安备 33010602011771号