可迭代的对象,迭代器和生成器
python中所有生成器都是迭代器,不过不同在于迭代器用于从集合中取出元素,而生成器用于“凭空”生成元素。 本章就来探讨一下迭代,迭代器和生成器。
本章的notebook文件在这里
可迭代对象
但解释器需要迭代对象x
时,会自动调用iter(x)
,其机制依次如下:
- 检测对象是否实现
__iter__
,是则调用它返回迭代器。
- 检查对象是否实现了
__getitem__
,是Python会创建一个迭代器,尝试按照顺序获取元素。
- 如果尝试失败,Python会抛出
TypeError
,显示对象不可迭代。
因此Python中所有序列都可以迭代的原因就是它们都实现了__getitem___
方法。
可迭代对象和迭代器对比
可迭代对象指的是:使用iter
内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__
方法,那么他就是可迭代的;同时如果实现了__getitem__
方法且参数从零开始的索引,这种对象也可以迭代。
下面我们用while
语句来演示一下迭代过程:
1 2 3 4 5 6 7 8 9
| s = 'ABC' it = iter(s) while True: try: print(next(it)) except StopIteration: del it break
|
A
B
C
标准迭代器有两个方法:
__next__
返回下一个可用元素,如果没有则抛出StopIteration
__iter__
返回self
,以便在应该使用可迭代对象的地方使用迭代器,例如for
循环
ps 熟悉C++的同学应该对这里很熟悉了。
这里需要注意的是,可迭代对象的__iter__
返回的是迭代器,迭代器的__iter__
返回的是本身。
因此我们在构建可迭代对象和迭代器时要注意二者的区别。
典型迭代器示例
下面给出一个典型的迭代器示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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): return SentenceIterator(self.words)
class SentenceIterator:
def __init__(self, words): self.words = words self.index = 0
def __next__(self): try: word = self.words[self.index] except IndexError: raise StopIteration() self.index += 1 return word
def __iter__(self): return self
|
1 2 3
| s = Sentence('Hi, I am Gaoshan. Nice to meet you.') for i in s: print(i)
|
Hi
I
am
Gaoshan
Nice
to
meet
you
注意上述示例中迭代器和可迭代对象的区别,在设计模型中,迭代器模式常用来:
- 访问一个聚合对象的内容而无需暴露它的内部表示
- 支持对聚合对象的多种遍历
- 为遍历不同的聚合结构提供一个统一的接口(多态迭代)
当然更加符合Python风格的习惯是实现一个生成器函数。
生成器函数
下面的代码给出了生成器函数的一个示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
|
1 2 3
| s = Sentence('How you doing~') for i in s: print(i)
|
How
you
doing
只要函数定义体中有yield
关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象,即生成器函数是生成器工厂。
下面以一个简单的例子来说明生成器的行为:
1 2 3 4 5
| def gen_123(): yield 1 yield 2 yield 3
|
<function __main__.gen_123>
<generator object gen_123 at 0x000001CDA772F780>
1
2
3
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-14-5f315c5de15b> in <module>()
----> 1 next(g)
StopIteration:
上面的例子我们生成器函数构建了一个生成器,将其传给next
函数,接着生成器函数会向前执行,返回产出值,超出生成器函数定义体时会抛出StopIteration
异常。
下面示例其在for循环中的表现:
1 2 3 4 5 6 7 8
| def gen_AB(): print('start') yield 'A' print('continue') yield 'B' print('end') for c in gen_AB(): print('-->', c)
|
start
--> A
continue
--> B
end
惰性实现
所谓惰性实现,即每次生成一个元素。惰性求值(lazy evaluation)是一个编程语言上一个常用的术语。
之前实现的几个类都不具有惰性,因为在__init__
中就急迫都构建好的文本中所有单词列表,这样内存里就需要保存所有单词信息,当数据量特别大或者我们只需要迭代前几个元素时,这么做就不合时宜了。
下面利用re.finditer
(re.findall
的惰性版本)来展示前面例子的惰性实现:
1 2 3 4 5 6 7 8 9 10 11
| 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()
|
生成器表达式
生成器表达式可以理解为列表推导的惰性版本,它不会马上构建列表,而是返回一个生成器。
1 2 3 4 5 6 7 8 9
| def gen_AB(): print('start') yield 'A' print('continue') yield 'B' print('end')
res1 = [x*3 for x in gen_AB()]
|
start
continue
end
1 2
| for i in res1: print('-->', i)
|
--> AAA
--> BBB
1 2
| res2 = (x*3 for x in gen_AB())
|
<generator object <genexpr> at 0x000001CDA77B9678>
1 2
| for i in res2: print('-->', i)
|
start
--> AAA
continue
--> BBB
end
注意这里生成器表达式和列表推导的不同。
那么我们也可以用生成器表达式来重写这个类:
1 2 3 4 5 6 7 8 9 10
| 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))
|
标准库中的生成器函数
下面列出一些标准库中的常见生成器函数:
用于过滤的生成器函数
模块 |
函数 |
说明 |
itertools |
compress(it, selector_it) |
并行处理两个可迭代的对象;如果 selector_it 中的元素是真值, 产出 it 中对应的元素 |
itertools |
dropwhile(predicate, it) |
处理 it,跳过 predicate 的计算结果为真值的元素,然后产出剩下 的各个元素(不再进一步检查) |
(内置) |
filter(predicate, it) |
把 it 中的各个元素传给 predicate,如果 predicate(item) 返回真 值,那么产出对应的元素;如果 predicate 是 None,那么只产出真 值元素 |
itertools |
filterfalse(predicate, it) |
与 filter 函数的作用类似,不过 predicate 的逻辑是相反 的:predicate 返回假值时产出对应的元素 |
itertools |
islice(it, stop) 或 islice(it, start, stop, step=1) |
产出 it 的切片,作用类似于 s[:stop] 或 s[start:stop:step],不过 it 可以是任何可迭代的对象,而且这个函数实现的是惰性操作 |
itertools |
takewhile(predicate, it) |
predicate 返回真值时产出对应的元素,然后立即停止,不再继续 |
用于映射的生成器函数
模块 |
函数 |
说明 |
itertools |
accumulate(it, [func]) |
产出累积的总和;如果提供了 func,那么把前两个元素传给它,然后把 计算结果和下一个元素传给它,以此类推,最后产出结果 |
(内置) |
enumerate(iterable, start=0) |
产出由两个元素组成的元组,结构是 (index, item),其中 index 从 start 开始计数,item 则从 iterable 中获取 |
(内置) |
map(func, it1, [it2, …, itN]) |
把 it 中的各个元素传给func,产出结果;如果传入 N 个可迭代的对象, 那么 func 必须能接受 N 个参数,而且要并行处理各个可迭代的对象 |
itertools |
starmap(func, it) |
把 it 中的各个元素传给 func,产出结果;输入的可迭代对象应该产出可 迭代的元素 iit,然后以 func(*iit) 这种形式调用 func |
用于合并多个对象的生成器函数
模块 |
函数 |
说明 |
itertools |
chain(it1, …, itN) |
先产出 it1 中的所有元素,然后产出 it2 中的所有元素,以此类推, 无缝连接在一起 |
itertools |
chain.from_iterable(it) |
产出 it 生成的各个可迭代对象中的元素,一个接一个,无缝连接在 一起;it 应该产出可迭代的元素,例如可迭代的对象列表 |
itertools |
product(it1, …, itN, repeat=1) |
计算笛卡儿积:从输入的各个可迭代对象中获取元素,合并成由 N 个元素组成的元组,与嵌套的 for 循环效果一样;repeat 指明重复 处理多少次输入的可迭代对象 |
(内置) |
zip(it1, …, itN) |
并行从输入的各个可迭代对象中获取元素,产出由 N 个元素组成的 元组,只要有一个可迭代的对象到头了,就默默地停止 |
itertools |
zip_longest(it1, …, itN, fillvalue=None) |
并行从输入的各个可迭代对象中获取元素,产出由 N 个元素组成的 元组,等到最长的可迭代对象到头后才停止,空缺的值使用 fillvalue 填充 |
把输入的各个元素拓展成多个输出的生成器函数
模块 |
函数 |
说明 |
itertools |
combinations(it, out_len) |
把 it 产出的 out_len 个元素组合在一起,然后产出 |
itertools |
combinations_with_replacement(it, out_len) |
把 it 产出的 out_len 个元素组合在一起,然后产出,包 含相同元素的组合 |
itertools |
count(start=0, step=1) |
从 start 开始不断产出数字,按 step 指定的步幅增加 |
itertools |
cycle(it) |
从 it 中产出各个元素,存储各个元素的副本,然后按顺 序重复不断地产出各个元素 |
itertools |
permutations(it, out_len=None) |
把 out_len 个 it 产出的元素排列在一起,然后产出这些 排列;out_len 的默认值等于 len(list(it)) |
itertools |
repeat(item, [times]) |
重复不断地产出指定的元素,除非提供 times,指定次数 |
用于重新排列元素的生成器函数
模块 |
函数 |
说明 |
itertools |
groupby(it,key=None) |
产出由两个元素组成的元素,形式为 (key, group),其中 key 是分组标 准,group 是生成器,用于产出分组里的元素 |
(内置) |
reversed(seq) |
从后向前,倒序产出 seq 中的元素;seq 必须是序列,或者是实现了 reversed 特殊方法的对象 |
itertools |
tee(it, n=2) |
产出一个由 n 个生成器组成的元组,每个生成器用于单独产出输入的可 迭代对象中的元素 |
yield from
yield from
是yield
的一个语法糖,下面两种写法是等价的:
1 2 3 4 5
| def chain(*iterables): for it in iterables: for i in it: yield i list(chain('ABC', 'DE'))
|
['A', 'B', 'C', 'D', 'E']
1 2 3 4
| def chain(*iterables): for it in iterables: yield from it list(chain('ABC', 'DE'))
|
['A', 'B', 'C', 'D', 'E']
可迭代的规约函数
下面的规约函数都可以接受一个可迭代对象并返回单个结果。
理论上来说,所有的规约函数都可以用reduce
来实现,但是对于例如any
,all
这类函数,它们都可以短路(即不用完全遍历完可迭代对象就提前返回),而reduce
做不到。
模块 |
函数 |
说明 |
(内置) |
all(it) |
it 中的所有元素都为真值时返回 True,否则返回 False;all([]) 返回 True |
(内置) |
any(it) |
只要 it 中有元素为真值就返回 True,否则返回 False;any([]) 返回 False |
(内置) |
max(it, [key=,] [default=]) |
返回 it 中值最大的元素;*key 是排序函数,与 sorted 函数中的一样;如果可 迭代的对象为空,返回 default |
(内置) |
min(it, [key=,] [default=]) |
返回 it 中值最小的元素;#key 是排序函数,与 sorted 函数中的一样;如果可 迭代的对象为空,返回 default |
functools |
reduce(func, it, [initial]) |
把前两个元素传给 func,然后把计算结果和第三个元素传给 func,以此类 推,返回最后的结果;如果提供了initial,把它当作第一个元素传入 |
(内置) |
sum(it, start=0) |
it 中所有元素的总和,如果提供可选的 start,会把它加上(计算浮点数的加 法时,可以使用 math.fsum 函数提高精度) |
深入iter函数
iter
函数有一个鲜为人知的用法:传入两个参数来创建迭代器,第一个参数是可调用对象,用于产出值;第二个是哨符,当可调用对象返回该值时,触发迭代器抛出StopIteration
异常而不产出哨符。
例如,我们玩飞行棋时需要一直掷骰子直到6才能出动飞机,下面我们展示一个一直掷骰子直到掷出6的过程。
1 2 3 4 5
| from random import randint def d6(): return randint(1, 6) d6_iter = iter(d6, 6) d6_iter
|
<callable_iterator at 0x1cda774ae48>
1 2
| for roll in d6_iter: print(roll)
|
4
2
1
5
2
3
2
3
3
2
3