当我在自己的程序中发现用到了模式,我觉得这就表明某个地方出错了。程序的形
式应该仅仅反映它所要解决的问题。代码中其他任何外加的形式都是一个信号,(至
少对我来说)表明我对问题的抽象还不够深——这通常意味着自己正在手动完成的
事情,本应该通过写代码来让宏的扩展自动实现。
——Paul Graham Lisp 黑客和风险投资人
迭代是数据处理的基石。我们需要一种数据模型,能按照需要一个一个的获取元素,这就是迭代器模式。yield关键字用来构成生成器,在python社区,大部分时候都把迭代器和生成器划等号。
迭代器支持一下操作:
* for循环
* 构建和扩展集合类型
* 逐行遍历文本文件
* 列表推导、字典退保、集合推导
* 元祖拆包
* 使用*拆包给函数传参
Sentence类第一版:单词序列
我们要实现一个Sentence类,然后逐渐满足迭代器支持的操作。给他传入一些包含文本的字符串,然后逐个进行单词迭代。
1 import re 2 import reprlib 3 WORDRE = re.compile(r'\w+') 4 class Sentence: 5 def __init__(self, text): 6 self._words = WORDRE.findall(text) 7 self._text = text 8 def __getitem__(self, index): 9 return self._words[index] 10 11 def __len__(self): 12 return len(self._words) 13 14 def __repr__(self): 15 return 'Sentence(%s)' %(reprlib.repr(self._text)) 16 17 sentence = Sentence("hello world, nice to see you.") 18 print(repr(sentence)) 19 print(sentence[2]) 20 print(sentence._words) 21 for word in sentence: 22 print(word)
sentence实现了序列的基本协议,而且可以迭代,但是为什么?
序列可以迭代的原因:iter函数
python解释器在需要迭代实例对象时,会自动调用iter()方法。
iter函数的执行逻辑如下:
1)检查对象是否实现了__iter__方法,如果实现了,就调用它,获得一个迭代器
2)如果没有__iter__方法,但是实现了__getitem__方法,python会自动创建一个迭代器,尝试从0开始顺序获取元素
3)如果尝试也失败了,那么python会抛出TypeError异常,通常会提示"C object is not iterable"
python序列都可迭代的原因就是它们都实现了__getitem__方法,标准序列也都实现了__iter__方法。
鸭子类型的极端形式:不仅要实现特殊的__iter__方法,还要实现__getitem__方法,这样才认为对象是可迭代的。
白鹅理论中,可迭代对象的定义简单些,只要实现了__iter__方法,对象就是可迭代的。
可迭代对象与迭代器
可迭代的对象
使用iter内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__方法,那么该对象就是可迭代的。序列都可以迭代;实现了__getitem__方法,且支持从0开始索引的对象也可以迭代。
可迭代的对象和迭代器的关系:python可以从可迭代的对象中获取迭代器。
1 s = 'abcde' 2 for ch in s: 3 print(ch) 4 5 it = iter(s) 6 print(it) #it是一个迭代器 7 8 while True: 9 try: 10 print(next(it)) 11 except StopIteration: 12 print("done") 13 break
使用内置函数iter(obj)来获取可迭代对象的迭代器it,使用next(it)来获取可迭代对象的下一个元素,如果没有元素了,迭代器会抛出StopIteration异常。
标准的迭代器需要支持两个方法:
__iter__
返回self,以便在使用可迭代对象的地方使用迭代器
__next__
返回下一个可用的元素,如果没有元素了,抛出SopIteration异常
迭代器
迭代器是对象:实现了__next__方法,返回下一个可用元素,如果没有元素了,抛出StopIteration异常。
Sentence类第二版:典型的迭代器
修改Sentence类使得它可以迭代,因此要实现__iter__方法,返回一个SentenceIterator类的迭代器实例,二SentenceIterator则需要实现__next__方法。
之所以这么做,主要是为了说了可迭代对象跟迭代器的区别,实际上是没有必要的。
1 import re 2 import reprlib 3 4 WORDRE = re.compile(r'\w+') 5 class Sentence: 6 def __init__(self, text): 7 self._words = WORDRE.findall(text) 8 self._text = text 9 10 def __repr__(self): 11 return 'Sentence(%s)' %(reprlib.repr(self._text)) 12 13 def __iter__(self): 14 return SentenceIter(self._words) 15 16 class SentenceIter: 17 def __init__(self, words): 18 self._words = words 19 self._index = 0 20 21 def __next__(self): 22 if self._index >= len(self._words): 23 raise StopIteration 24 self._index += 1 25 return self._words[self._index-1] 26 27 sentence = Sentence("hello world, nice to see you!") 28 it = iter(sentence) 29 print(it) #SentenceIter对象 30 31 for i in range(10): 32 print(next(it))
在for循环中,当next迭代完全部的元素之后,最终就会抛出StopIteration异常。
有些可能会想在Sentence类中实现__next__方法,然后Sentence实例即是可迭代对象,又是迭代器。然而,这种想法非常糟糕,根据有大量代码审查经验的Alex Martelli所说,这是常见的反模式。
引用《设计模式:可复用面向对象软件的基础》中的话:
迭代器模式可用来:
• 访问一个聚合对象的内容而无需暴露它的内部表示
• 支持对聚合对象的多种遍历
• 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)
为了“支持多种遍历”,必须能够从同一个实例中获取多个独立的迭代器,每个迭代器要维护自身的状态,因此这种模式的正确实现方式是新创建一个迭代器。这也是为什么需要定义SentenceIter。
总结:
可迭代的对象一定不能是自身的迭代器。也就是说可迭代对象一定要实现__iter__方法,但不能实现__next__方法。而迭代器应该是可以一直迭代,迭代器的__iter__方法要返回自身。
Sentence类第三版:生成器函数
实现与上面相同的功能但更加pythonic的方式是利用生成器函数取代SentenceIter类。
1 import re 2 import reprlib 3 4 WORDRE = re.compile(r'\w+') 5 class Sentence: 6 def __init__(self, text): 7 self._words = WORDRE.findall(text) 8 self._text = text 9 10 def __repr__(self): 11 return 'Sentence(%s)' %(reprlib.repr(self._text)) 12 13 def __iter__(self): 14 for word in self._words: 15 yield word 16 17 18 19 sentence = Sentence("hello world, nice to see you!") 20 21 for word in sentence: 22 print(word)
生成器函数的工作原理
只要python函数的定义体中有yield关键字,那么该函数就是一个生成器。调用生成器函数时,会返回一个生成器对象。
当使用next调用生成器对象时,生成器函数会执行,直到执行完毕一个yield语句,然后返回yield后面的值,不再执行,直到下一次调用next时,再从上次暂停的地方继续往下执行,当整个函数体执行完毕时,生成器对象抛出StopIteration异常。
1 def gen_ABCD(): 2 print("---1---") 3 yield 'A' 4 print("---2---") 5 yield 'B' 6 print("---3---") 7 yield '3' 8 print("---4---") 9 yield 'D' 10 11 genobj = gen_ABCD() 12 13 for i in genobj: 14 print('iter gen:', i) 15 16 print(next(genobj)) #这里会抛出StopIteration异常
Sentence.__iter__是个生成器函数,在调用时产生一个生成器对象,然后for循环迭代时,执行函数体内容,遇到yield就返回,因此不需要再额外定义一个迭代器SentenceIter了。
Sentence类第四版:惰性实现
目前Sentence实现都是一次性把所有的单词都列了出来,但是如果我们绝大多数情况都只会用到前面一两个,那或许是有点白费力气了。在python3,思考某件事有没有懒惰的方式,答案通常都是肯定的。
re.finditer是re.findall的惰性版本,返回的是一个迭代器,可以利用它实现Sentence的惰性特点。
1 import re 2 import reprlib 3 4 WORDRE = re.compile(r'\w+') 5 class Sentence: 6 def __init__(self, text): 9 self._text = text 10 11 def __repr__(self): 12 return 'Sentence(%s)' %(reprlib.repr(self._text)) 13 14 def __iter__(self): 15 for word in WORDRE。finditer(self._text): 16 yield word.group()
Sentence类第五版:生成器表达式
简单的生成器函数可以替换成生成器表达式。
生成器表达式可以理解成列表推导的惰性版本,他不会立刻求值,而是在你真正需要的时候,才去生成所需要的元素。
1 import re 2 import reprlib 3 4 WORDRE = re.compile(r'\w+') 5 class Sentence: 6 def __init__(self, text): 7 self._text = text 8 9 def __repr__(self): 10 return 'Sentence(%s)' %(reprlib.repr(self._text)) 11 12 def __iter__(self): 13 return (word.group() for word in WORDRE.finditer(self._text))
使用生成器表达式没有了yield,但是效果是一样的,iter(obj)到到一个生成器对象。
生成器表达式是语法糖,完全可以替换成生成器函数。不过有些时候,使用生成器表达式更方便。
何时使用生成器表达式
生成器表达式是创建生成器对象的简洁方式;生成器函数更灵活,可以实现复杂的逻辑,也可以作为协程(后续会介绍)使用。
如果逻辑很简单,一眼就能看明白,那么推荐使用生成器表达式;如果代码要分成多行,有循环、分支结构,那么建议定义成生成器函数,提高代码可读性。
等差数列生成器
迭代器模式的作用很简单---遍历数据结构。
下面我们定义一个等差数列类ArithmeticProgression:
1 import time 2 from fractions import Fraction 3 from decimal import Decimal 4 class ArithmeticProgression: 5 def __init__(self, start, step, end=None): 6 self._start = start 7 self._end = end 8 self._step = step 9 10 def __iter__(self): 11 cur = self._start 12 while self._end is None or cur < self._end: 13 yield cur 14 cur += self._step 15 16 17 ap = ArithmeticProgression(0, 1, 3) 18 print(list(ap)) 19 20 ap = ArithmeticProgression(1, .5, 3) 21 print(list(ap)) 22 23 ap = ArithmeticProgression(0, Fraction(1, 3), 1) 24 print(list(ap)) 25 26 ap = ArithmeticProgression(0, Decimal('.1'), .3) 27 print(list(ap)) 28 29 ap = ArithmeticProgression(0, .5) #无穷数列 30 for i in ap: 31 print(i) 32 time.sleep(1)
如果类的作用仅仅实现生成器,那不如使用函数更方便。
1 def aritprog_gen(begin, step, end=None): 2 cur = begin 3 while end is None or cur < end: 4 yield cur 5 cur += step
itertools模块提供了许多有趣的生成器函数。
无穷元素的生成器函数:
itertools.count(start=0, step=1) --> start, start+step, start+2*step, ...
itertools.cycle(p) --> p0, p1, ... plast, p0, p1, ...
itertools.repeat(elem [,n]) --> elem, elem, ... 无穷重复elem或者重复n次终止
终止在输入序列嘴上时的生成器函数:
itertools.accumulate(p [, func]) --> func是接收两个参数的函数,p的每个元素作为func的第二个参数传递,func的返回值作为下一次的第一个参数,返回p0, func(p0, p1), func(func(p0, p1), p2), 例如itertools.accumulate(range(1, 10), operator.mul) -> 1, 2, 6, 24, 120, 720, ...
itertools.chain(p, q) --> p0, p1, ... plast, q0, q1, ...
itertools.compress(data, selectors) --> (d[0] if s[0]), (d[1] if s[1]), ...根据selectors的true or false,筛选data
itertools.dropwhile(pred, seq) --> seq[n], seq[n+1], ... starting when pred fails
itertools.islice(seq, [start,] stop [, step]) --> seq[start:stop:step)
itertools.filterfalse(pred, seq) --> elements of seq when pred(elem) is false, filter函数过滤序列后剩下的部分
itertools.starmap(fun, seq) --> fun(*seq[0]), fun(*seq[1]), ...
itertools.takewhile(pred, seq) --> seq[0], seq[1], ... until pred fails
itertools.zip_longest(p, q, ...) --> (p[0], q[0]), (p[1], q[1]), ... 按照序列最长的一个组成元组,可以用fillvalue指定元素个数不足时的默认值
组合生成器函数:
itertools.product (p, q, ... [repeat=1]) --> (p[0], q[0], ... plast[0]), (p[0], q[0], ... plast[1]), ... repeat表示把前面的所有序列重复多少次
itertools.permutation(p [, r]) --> 序列p的下一种排列方式
itertools.combinations(p, r) --> 从序列p中取出r个元素的组合,不允许重复
itertools.combinations_with_replacement(p, r) --> 从序列p中取出r个元素的组合, 语序重复
重新排列元素的生成器函数
itertools.groupby(it, key=None) --> 产出由两个元素组成的元素,形式为(key, group), key是分组标准(key(it[i])),group是生成器,用于产出分组里的元素
itertools.tee(it, n=2) --> 产出一个由n个生成器组成的元组,每个生成器用于单独产出输入的可迭代对象中的元素
使用itertools模块提供的生成器函数可以轻松实现上面的等差数列。
1 import itertools 2 3 #首项是1, 公差是3的等差数列 4 arit_gen = itertools.count(1, 3) 5 #满足小于100的所有项 6 for i in itertools.takewhile(lambda x:x<100, arit_gen): 7 print(i) 8 9 #也可以直接写成下面的形式 10 arit_gen = itertools.takewhile(lambda x:x<100, itertools.count(1, 3))
1 import itertools 2 def aritprog_gen(begin, step, end=None): 3 first = type(begin+step)(begin) 4 ap_gen = itertools.count(first, step) 5 if end is not None: 6 ap_gen = itertools.takewhile(lambda x:x<end, ap_gen) 7 return ap_gen 8 9 ap_gen = aritprog_gen(1, 3, 100) 10 print(list(ap_gen))
itertools模块中的生成器函数的用法:
1 import itertools 2 import operator 3 def vowel(c): 4 return c.lower() in 'aeiou' 5 6 print(list(filter(vowel, 'Aardvark'))) 7 print(list(itertools.filterfalse(vowel, 'Aardvark'))) 8 print(list(itertools.takewhile(vowel, 'Aardvark'))) 9 print(list(itertools.dropwhile(vowel, 'Aardvark'))) 10 print(list(itertools.compress('Aardvark', (1,0,1,1,0,1)))) 11 print(list(itertools.islice('Aardvark', 4))) 12 print(list(itertools.islice('Aardvark', 1, 6, 2))) 13 14 sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1] 15 print(list(itertools.accumulate(sample))) 16 print(list(itertools.accumulate(sample, min))) 17 print(list(itertools.accumulate(sample, max))) 18 print(list(itertools.accumulate(sample, operator.mul))) 19 20 #enumerate(iterable, start=0) --> (start, it[0]), (start, it[1]), ... 21 print(list(enumerate('albatroz', 5))) 22 print(list(map(operator.mul, range(11), range(11)))) 23 print(list(map(lambda a, b:(a,b), range(11), [2, 4, 8]))) 24 25 #'a'*1-> 'a', 'l'*2 -> 'll', 'b'*3 -> 'bbb', ... 26 print(list(itertools.starmap(operator.mul, enumerate('albatro', 1)))) 27 28 print(list(itertools.starmap(lambda a,b:b/a, enumerate(itertools.accumulate(sample), 1)))) 29 30 print(list(itertools.chain('ABC', range(2)))) 31 print(list(itertools.chain(enumerate('ABC')))) 32 print(list(itertools.chain.from_iterable(enumerate('ABC')))) 33 print(list(zip('ABC', range(5)))) 34 print(list(zip('ABC', range(5), [10,20,30,40]))) 35 print(list(itertools.zip_longest('ABC', range(5)))) 36 print(list(itertools.zip_longest('ABC', range(5), fillvalue='?'))) 37 38 print(list(itertools.product('ABC', range(2)))) 39 suits = 'spades hearts diamonds clubs'.split() 40 print(list(itertools.product('AK', suits))) 41 print(list(itertools.product('ABC'))) 42 print(list(itertools.product('ABC', repeat=2))) 43 print(list(itertools.product('ABC', range(3), repeat=2))) 44 print(list(itertools.product(range(2), repeat=3))) 45 print(list(itertools.product('AB', range(2), repeat=2))) 46 47 48 print(list(itertools.combinations('ABC', 2))) 49 print(list(itertools.combinations_with_replacement('ABC', 2))) 50 print(list(itertools.permutations('ABC', 2))) 51 print(list(itertools.product('ABC', repeat=2))) 52 53 print(list(itertools.groupby('LLLLAAGGG'))) 54 55 for char, group in itertools.groupby('LLLLAAGGG'): 56 print(char, '-->', group) 57 58 animals = ['duck', 'eagle', 'rat', 'giraffe', 'bear', 'bat', 'dolphin', 'shark', 'lion'] 59 animals.sort(key=len) 60 print(animals) 61 62 #按照长度对animals分组 63 for length, group in itertools.groupby(animals, key=len): 64 print(length, '->', list(group)) 65 66 #按照对10取余对nums分组 67 nums = [2, 4, 8, 13, 21, 57, 96, 103, 77, 17, 6] 68 for mod, group in itertools.groupby(nums, key=lambda x:x%10): 69 print(mod, '->', list(group)) 70 71 g1, g2 = itertools.tee('ABC') 72 print(next(g1), next(g1), next(g1)) 73 print(next(g2), next(g2), next(g2)) 74 75 print(list(zip(*itertools.tee('ABC'))))
上面的例子主要是告诉我们,要生成生成器是先要知道标准库中提供了哪些生成器函数避免"重新造轮子"这种吃力不讨好的事情。
yield from
如果生成器函数要产出另一个生成器生成的值,传统方法使用for循环。
1 def chain(*iterables): 2 for it in iterables: 3 for i in it: 4 yield i 5 6 ch = chain('ABC', range(4)) 7 print(list(ch))
从python3.3开始,引入了新语法:yield from,可以从生成器中挨个返回元素,知道所有的元素被产出为止。
一次yield from 可以完全替代内部的for循环。
1 def chain(*iterables): 2 for it in iterables: 3 yield from it
yield from除了可以取代循环外,还会创建通道,把内层生成器直接与外层生成器的客户端连接起来。把生成器当成协程使用时,这个通道特别重要,不仅能为客户端代码生成值,还能使用客户端提供的值。关于协程会在后续博文中讲到。
可迭代的规约函数
之前使用过的max、min、sum、all、any都是规约函数。这些规约函数都可以使用functools.reduce实现。
all(it) --> it中的元素全部为真时返回True,否则返回False;all([])返回True,空列表、空元组、空字典都返回True要注意,(python的帮助文档就是这么写的)
any(it) --> it中的元素有一个为真时返回True,否则返回False;any([]),any(()),any({})返回False
max(it, [key=,] [default=]) --> 返回it中最大的元素,key是排序函数;若it为空,返回default
min(it, [key=,] [default=]) --> 返回it中最小的元素,key是排序函数;若it为空,返回default
functools.reduce(func, it [initial]) --> 把前两个元素传给func,然后把结果和第三个元素传给func,一次类推,如果提供了initial,把它当成第一个元素
sum(it, start=0) --> 返回it中元素中的和,如果提供start,那在加上start
1 print(all([1,2,3])) #True 2 print(all([1,0,3])) #False 3 print(all([])) #True 4 5 print(any([1,2,3])) #True 6 print(any([1,0,3])) #True 7 print((any[])) #False 8 9 g = (n for n in [0, 0.0, 7, 8]) 10 print(any(g)) #True 11 print(next(g)) #8,因为any(g)在生成器函数运行到7的时候停止了返回7
深入分析iter函数
通过前面的学习,我们知道iter(iterable)可以返回一个迭代器,然而查看帮助文档发现iter还有另一种用法。
iter(...)
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.
iter接收一个参数可调用对象,用来不断调用产出值,另一个参数是哨符,这是个标志值,当可调用参数返回这个值的时候,会触发StopIteration异常。
1 import random 2 def f(): 3 return random.choice(range(10)) 4 5 it = iter(f, 9) 6 for i in it: 7 print(i) 8 9 #while True: 10 # print(next(it)) #当f()返回9的时候抛出StopIteration异常
定义了一个函数f,每次调用随机的从[1, 9]挑选一个数字,iter(f, 9)返回一个callable_iterator对象,代码中的循环也许会运行较长时间,但肯定不会打印9,因为9是哨符,f函数如果返回9,就一定会结束for循环或者抛出StopIteration异常。
生成器与协程关系
python在引入了yield、yield from关键字之后,不就又添加了一个新的生成器的方法,他就是.send()方法。
与.__next__方法一样,.send()也会使生成器函数往下运行到下一个yield语句处。不过,.send()方法还允许使用生成器的客户把数据发给生成器,即不管传给.send()什么参数,那个参数都会成为生成器yield表达式的值,换而言之,.send()方法可以实现 生成器和调用生成器的客户之间的数据交换。这一项重大"改进",甚至改变了生成器本身的性质:生成器可以作为协程使用。但是绝不应该混淆两者的概念,引用David的话:
所以这里不做深入讨论,仅仅对生成器和协程做个简单介绍。