可迭代的对象、迭代器、生成器

当我在自己的程序中发现用到了模式,我觉得这就表明某个地方出错了。程序的形
式应该仅仅反映它所要解决的问题。代码中其他任何外加的形式都是一个信号,(至
少对我来说)表明我对问题的抽象还不够深——这通常意味着自己正在手动完成的
事情,本应该通过写代码来让宏的扩展自动实现。
                   ——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的话:

所以这里不做深入讨论,仅仅对生成器和协程做个简单介绍。

转载于:https://www.cnblogs.com/forwardfeilds/p/10489384.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值