《Fluent Python》笔记 迭代器和生成器

迭代器用于从集合中取出元素; 而生成器用于“凭空”生成元素。

可迭代对象

解释器需要迭代对象 x 时, 会自动调用 iter(x)
内置的 iter 函数有以下作用:
(1) 检查对象是否实现了 __iter__ 方法, 如果实现了就调用它, 获取一个迭代器对象。
(2) 如果没有实现 __iter__ 方法, 但是实现了 __getitem__ 方法,Python 会创建一个迭代器, 尝试按顺序(从索引 0 开始) 获取元素。
(3) 如果尝试失败, Python 抛出 TypeError 异常, 通常会提示“C object is not iterable”(C 对象不可迭代), 其中 C 是目标对象所属的类。

正因为序列都实现了 __getitem__ 方法,所以序列对象可迭代。如果实现了__iter__方法,能够给返回对象自身的迭代器,那么这个对象也是可迭代对象。

可迭代对象与迭代器的关系:Python从可迭代对象中获取迭代器

迭代器

使用iter()函数从可迭代对象a中构建迭代器b:b = iter(a)

使用next()函数使用迭代器b:next(b)

迭代器对象中实现了__next__方法,返回可迭代对象中的下一个元素;如果没有元素了,那么抛出 StopIteration 异常。迭代器还实现了 __iter__ 方法, 因此迭代器也可以迭代。

下面示例展示了典型的迭代器模式实现一个可迭代对象(Sentence类对象)。

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
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

可迭代的对象一定不能是自身的迭代器。即可迭代的对象必须实现 __iter__ 方法, 但不能实现 __next__ 方法。另一方面, 迭代器应该一直可以迭代。 迭代器的 __iter__ 方法应该返回自身。

生成器函数

使用生成器函数实现和上面迭代器示例一样的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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):
for word in self.words: # 迭代序列
yield word # 产出元素
return

上面代码中__iter__方法的定义体中有yield关键字,即__iter__方法是一个生成器函数。调用生成器函数时,会返回一个生成器对象。

只要 Python 函数中包含关键字 yield, 该函数就是生成器函数。

生成器是迭代器(因为生成器完全实现了迭代器接口), 会生成传给 yield 关键字的表达式的值。对于生成器函数创建的生成器对象, 我们可以理解为其包装了生成器函数的定义体。 把生成器传给 next() 函数时, 生成器函数会向前, 执行函数定义体中的下一个 yield 语句, 返回产出的值, 并在函数定义体的当前位置暂停。 最终, 函数的定义体返回时, 外层的生成器对象会抛出StopIteration 异常——这一点与迭代器协议一致。

当通过for循环进行迭代时, for 机制的作用与 g = iter(gen_AB()) (其中gen_AB为生成器函数名)一样, 用于获取生成器对象, 然后每次迭代时调用 next(g)

生成器表达式

生成器表达式可以理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。因此,使用生成器表达式构建列表更加节省内存。

在前面的序列类型提到,列表推导两边是方括号,而生成器表达式两边是圆括号。

生成器表达式是构建一个生成器对象,和生成器函数返回的生成器对象一样。

标准库中的生成器函数

  • 用于过滤的生成器函数

    模块 函数 说明
    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 个生成器组成的元组, 每个生成器用于单独产出输入的可迭代对象中的元素。

上述生成器函数,其参数是生成器对象,返回也是生成器对象,故可以组合使用。

可迭代的归约函数

如果一个函数接受一个可迭代的对象,然后返回单个结果,那么就可以叫这个函数为“归约”函数、 “合拢”函数或“累加”函数。

常见的归约函数如下:

模块 函数 说明
(内置) 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函数的特殊用法

Python 中迭代对象 x 时会调用 iter(x)得到一个迭代器对象。除此之外,iter函数还有一个用法,示例代码如下:

1
2
3
with open('mydata.txt') as fp:
for line in iter(fp.readline, '\n'):
process_line(line)

这里iter函数传入两个参数, 使用常规的函数或任何可调用的对象创建迭代器。 第一个参数必须是可调用的对象(fp.readline), 用于不断调用(没有参数)产出各个值; 第二个值是哨符('\n'), 这是个标记值,当可调用的对象返回的值等于哨符时(读取到空行或文件末尾),触发迭代器抛出 StopIteration 异常(迭代结束), 而不产出这个值。