可迭代的对象,迭代器和生成器

python中所有生成器都是迭代器,不过不同在于迭代器用于从集合中取出元素,而生成器用于“凭空”生成元素。 本章就来探讨一下迭代,迭代器和生成器。

本章的notebook文件在这里

可迭代对象

但解释器需要迭代对象x时,会自动调用iter(x),其机制依次如下:

  1. 检测对象是否实现__iter__,是则调用它返回迭代器。
  2. 检查对象是否实现了__getitem__,是Python会创建一个迭代器,尝试按照顺序获取元素。
  3. 如果尝试失败,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)) # 不断在迭代器上使用next函数,获取下一元素
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 # 生成word
return # 该句不是必要的,生成器不会抛出StopIteraction异常,而是在生成全部值后直接退出

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
1
gen_123 # 一个函数对象
<function __main__.gen_123>
1
gen_123() # 调用它会返回一个生成器 
<generator object gen_123 at 0x000001CDA772F780>
1
2
g = gen_123()
next(g)
1
1
next(g)
2
1
next(g)
3
1
next(g)
---------------------------------------------------------------------------

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.finditerre.findall的惰性版本)来展示前面例子的惰性实现:

1
2
3
4
5
6
7
8
9
10
11
class Sentence:

def __init__(self, text):
self.text = text # 这里我们不需要在初始时构建word列表

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
# 生成器表达式, 注意这里的惰性(没有输出start等信息  )
res2 = (x*3 for x in gen_AB())
1
res2 # 得到一个生成器 
<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 fromyield的一个语法糖,下面两种写法是等价的:

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来实现,但是对于例如anyall这类函数,它们都可以短路(即不用完全遍历完可迭代对象就提前返回),而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