迭代器模式:惰性获取数据项的方式。按需一次获取一个数据项,可处理内存中放不下的数据集。

所有生成器都是迭代器,生成器完全实现了迭代器接口。但根据定义,迭代器用于从集合中取出元素,而生成器用于生成元素

14.1 Sentence 类第 1 版:单词序列

  • 解释器迭代对象时会调用 iter(x),其优先调用对象的 __iter__ 方法(返回迭代器),若没有则尝试使用 __getitem__ 按顺序获取元素,以此创建一个迭代器
  • __iter__ 方法是 Iterable 抽象基类的钩子——无需注册即可成为子类。因此通过 __getitem__ 实现的可迭代对象不是 Iterable 的子类
  • 检查是否可迭代的最准确方法是调用 iter(x) 函数,并尝试捕获异常

14.2 可迭代的对象与迭代器的对比

  • 可迭代的对象:如果实现了能返回迭代器__iter__ 方法,则对象是可迭代的;
  • for 循环展开,展示迭代器的机制——next(it)StopIteration 异常:
s = 'ABC'
it = iter(s)   # construct an iterator
while True:
    try:
        print(next(it))   # use next(it) to get the next element
    except StopIteration: # StopIteration Error will be raised in the end
        del it            # release the iterator reference
        break
  • Iterator 继承自 IterableIterable 定义了抽象方法 __iter__(可迭代对象可以调用 iter() 函数);Iterator__iter__ 定义为返回自身,新增了抽象方法 __next__ 用于迭代,返回下个元素或抛出异常;
  • __iter____next__ 方法是 Iterable 抽象基类的钩子——实现了即为迭代器。特别地,迭代器也是可迭代的。

14.3 Sentence 类第 2 版:典型的迭代器

  • 根据迭代器设计模式,为了支持多种遍历,必须能从同一个可迭代的实例中获取多个独立的迭代器,各自维护自身的内部状态。因此需要设计独立的迭代器类型,不能是它本身;
  • 更符合 Python 习惯的方式是使用生成器函数来替代自定义的迭代器类型。

14.4 Sentence 类第 3 版:生成器函数

  • 可以将 __iter__ 定义为返回生成器对象——每次调用时会自动创建,这里的 __iter__ 方法是生成器函数
    def __iter__(self):  
        for word in self.words:  # <1>  
            yield word  # <2>  
        return  # <3>
  • 只要函数定义体中有 yield 关键字,该函数就是生成器函数。调用时会返回生成器对象,包装了生成器函数的定义体。它实现了迭代器接口,迭代方式与前面相同:
def gen_123():
    yield 1
    yield 2
    yield 3

for i in gen_123():
    print(i)         # 1 2 3
  • 调用 next() 函数时,生成器函数向前执行,直到函数定义体中的下一个 yield 语句,返回产出的值,并在函数定义体当前位置暂停。定义体执行完毕后抛出 StopIteration 异常;
  • 在 Python 3.3 之后,如果生成器函数有返回值,则可以通过异常对象获取值。

14.5 Sentence 类第 4 版:惰性实现

懒惰求值(Lazy Evaluation)与及早求值(Eager Evaluation)是编程理论的技术术语。

  • 小技巧:re.finditerre.findall 的懒惰版本,返回一个生成器,可节省大量内存。其生成的 re.MatchObject 实例可以利用 .group() 方法提取具体文本。

14.6 Sentence 类第 5 版:生成器表达式

  • 生成器表达式的效果与生成器函数完全等价,其得到的值即为生成器对象;
  • 生成器可以嵌套使用,只有真正循环迭代时,最内部生成器的函数体才会执行(惰性)。

14.7 何时使用生成器表达式

  • 生成器表达式语法更简洁;生成器函数更灵活,可以用多语句实现复杂逻辑,具有函数名称可以重用,也可以作为协程使用
  • 如果函数只有一个实参,且该实参为生成器表达式,则可以省略一对括号
  • 迭代器与生成器的实际运用总结:
    • range(5)[1,2] 等可迭代对象不是迭代器,可以通过 iter(x) 得到迭代器。内置类型的迭代器都有特殊的类型名,如 range_iteratorlist_iterator 等;
    • 生成器是迭代器,是可迭代对象。生成器可以通过生成器表达式直接得到,也可以通过调用生成器函数得到;
    • for 循环使用的是迭代器

14.8 示例:等差数列生成器

  • itertools 模块包括许多生成器函数:
    • itertools.count(begin, step) 返回的生成器会生成无穷的等差数列
    • itertools.takewhile(predicate, gen) 返回一个新的生成器,其使用 gen 生成元素,直到 predicate 返回 False 为止
  • 小技巧:
    • type() 返回类对象,可以以此作为构造方法。因此可以用 type(B)(A) 的语句将 A 的类型强制转换B 的类型;
    • 处理浮点数的运算时,注意精度损失累积的问题,尽量避免浮点数的累加

14.9 标准库中的生成器函数

以下得到的生成器中,若输入可迭代对象长度不同,会在某个可迭代对象到头后停止。

以下的生成器函数基本都可以组合在一起嵌套使用。

用于限制过滤的生成器函数

  • itertools.compress(it, selector_it):产出 selector_it 对应位置为真it 中元素;
  • itertools.dropwhile(predicate, it):先跳过 predicate 为真的元素,后产出所有剩下的的元素,不再检查;
  • itertools.takewhile(predicate, it):产出 predicate 为真的元素,不满足时立即停止
  • filter(predicate, it):产出 predicate 为真的元素;predicate=None,则产出所有 it 中的真值元素
  • itertools.filterfalse(predicate, it):与 filter 相反;
  • itertools.islice(it, stop) / (it, start, stop, step=1):产出切片,是惰性操作

用于映射的生成器函数

  • itertools.accumulate(it, [func]):累加,默认为求和,但是会在每一位记录当前的累加结果,返回一个生成器,而非只返回一个值;
  • enumerate(iterable, start=0):产出元组 (index, item)
  • map(func, it1[, it2, ..., itN])it 作为参数传给 func可接受多个参数,并行处理;
  • itertools.starmap(func, it):对 it 产出的每个元素 iit*iit 作为参数传给 func
    • starmap(lambda a, b: b/a, enumerate(accumulate(sample), 1))) 可以用来求平均值

用于合并的生成器函数

  • itertools.chain(it1, ..., itN):无缝连接各可迭代对象;
  • itertools.chain.from_iterable(it)it 产出可迭代对象,将它们无缝连接;
  • zip(it1, ..., itN):产出由 N 个元素构成的元组,直到一个可迭代对象到头
  • itertools.zip_longest(it1, ..., itN, fillvalue=None):同 zipfillvalue 填充
  • itertools.product(it1, ..., itN, repeat=1):计算笛卡儿积,生成由 N*repeat 个元素构成的元组。repeat 表示前面的 N 个可迭代对象整体重复几次

用于扩展输入的生成器函数

  • itertools.count(start=0, step=1):不断产出等差数列
  • itertools.repeat(item, [times]):不断产出指定元素,直到指定次数(若有)。常用于给 map 函数提供固定参数
  • itertools.cycle(it):产出 it 中各元素,存储副本,然后按顺序重复不断地循环产出
  • itertools.combinations(it, out_len):选择组合,it 中选取 out_len 个不同元素,产出所有组合;
  • itertools.combinations_with_replacement(it, out_len):可以选取相同元素
  • itertools.permutations(it, out_len=None):选择排列,it 中选取 out_len 个不同元素并排列,产出所有的排列;
    • 以上三个生成器函数与 product 构成组合学生成器

用于分组排列的生成器函数

  • reversed(seq)只接受序列或实现 __reversed__ 的对象,倒序产出 seq 中元素;
  • itertools.groupby(it, key=None):产出不同的 (key, group)根据元素的 keyit 中的元素分组,分组得到的元素通过各生成器 group 得到。注意:使用前需要先用分组标准排序或分组,也就是说该函数做的只是划分
  • itertools.tee(it, n=2):产出 n 个生成器,分别可以单独产出输入对象中的元素。

14.10 Python 3.3 中新出现的句法:yield from

  • yield from 语句用于生成器函数中,可从后面的生成器中生成元素,代替嵌套循环;
  • 它还创建通道,把内层生成器和外层生成器的客户端联系起来

14.11 可迭代的归约函数

  • 归约函数:接受可迭代对象,返回单个结果。包含以下函数:
    • all(it):全为真时返回 Trueit 为空时返回 True具有短路特性,会停止迭代;
    • any(it):存在真时返回 Trueit 为空时返回 False具有短路特性,会停止迭代;
    • max/min(it, [key=], [default=]):返回最大/小值,键 keyit 为空时返回 default
    • sum(it, start=0):求总和,初始值为 start
    • math.fsum(it, start=0):同 sum,用于浮点数求和,提高精度
    • functools.reduce(func, it, [initial]):归约函数,两两求结果。若有 initial,则第一次计算 initialit 首元素的计算结果
  • sorted(it, [key=], [reversed=False]) 可以接受任意可迭代对象,返回列表

14.12 深入分析 iter 函数

  • iter(func, value) 会使用可调用对象和标记符创建迭代器callable_iterator 对象)(从结果上来看,这与 iter 函数的一般用途是一致的),它不断调用 func() 产出值,直到值为 value 时,抛出 StopIteration 异常;
  • iter 的用途:逐行读取文件,直到遇到空行或文件末尾:
with open('data.txt') as fp:
    for line in iter(fp.readline, '\n'):
        process_line(line)

14.13 案例分析:在数据库转换工具中使用生成器

  • 在文件读取处理的过程中,如果有多种读取、处理的方式,为了减小耦合性,可以使用生成器的方式读取并产出数据,再调用不同方法处理,把主要的选择逻辑在主函数中实现:
def main():
    reader = reader_gen1()    # reader_gen1 is a generator function
    # reader = reader_gen2()  # reader_gen2 is a generator function

    processer = process1      # process1 is a function
    # processer = process2    # process2 is a function
    
    for data in reader:       # main part
        processer(data)

def reader_gen1():
    yield 1

def reader_gen2():
    yield 2

def process1():
    pass

def process2():
    pass
  • 使用这种方式解耦读和写,支持交叉读写,无需一次性读取,可以处理任意大小的文件。这样相当于把文本内容看成数据流,随时读取处理。

14.14 把生成器当成协程

  • 生成器对象.send() 方法不仅使生成器继续执行到下一个 yield 语句,还支持使用生成器的客户通过方法的参数把数据发给自己,成为 yield 表达式的值。它允许在客户代码和生成器之间双向交换数据,这改变了生成器的本性,变为协程
  • 协程与迭代无关,会在第 16 章具体讨论。