Fluent Python 笔记 第 14 章 可迭代的对象、迭代器和生成器

迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)。本章说明 Python 语言是如何内置迭代器模式的,这样就避免了自己手动去实现。

在 Python 中,所有集合都可以迭代。在 Python 语言内部,迭代器用于支持:

  • for循环
  • 构建和扩展集合类型
  • 逐行遍历文本文件
  • 列表推导、字典推导和集合推导
  • 元组拆包
  • 调用函数时,使用 * 拆包实参

14.1 Sentence 类第1版:单词序列

序列可以迭代的原因:iter 函数

解释器需要迭代对象 x 时,会自动调用 iter(x)。内置的 iter 函数有以下作用:

  • 检查对象是否实现了 __iter__ 方法,如果实现了就调用它,获取一个迭代器。
  • 如果没有实现 __iter__ 方法,但是实现了 __getitem__ 方法,Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素。
  • 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C object is not iterable”(C
    对象不可迭代),其中 C 是目标对象所属的类。

从 Python 3.4 开始,检查对象 x 能否迭代,最准确的方法是:调用 iter(x) 函数,如果不可迭代,再处理 TypeError 异常。这比使用 isinstance(x, abc.Iterable) 更准确,因为 iter(x) 函数会考虑到遗留的 __getitem__ 方 法,而 abc.Iterable 类则不考虑。

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

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

标准的迭代器接口有两个方法。

__next__

返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常。

__iter__

返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。

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

这里之所以这么做,是为了清楚地说明可迭代的对象和迭代器之间的重要区别,以及二者之间的联系。

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

把 Sentence 变成迭代器:坏主意

可迭代的对象一定不能是自身的迭代器。也就是说,可迭代的对象必须实现 __iter__ 方法,但不能实现 __next__ 方法。

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

def __iter__(self):
    for word in self.words:
        yield word
    return

生成器函数的工作原理

只要 Python 函数的定义体中有 yield 关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。

14.5 Sentence 类第4版:惰性实现

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

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

惰性。

res2 = (x*3 for x in gen_AB())
def __iter__(self):
    return (match.group() for match in RE_WORD.finditer(self.text))

14.7 何时使用生成器表达式

如果生成器表达式要分成多行写,我倾向于定义生成器函数,以便提高可读性。此外,生成器函数有名称,因此可以重用。

14.8 另一个示例:等差数列生成器

self.begin 赋值给 result,不过会先强制转换成前面的加法算式得到的类型。

def __iter__(self):
    result = type(self.begin + self.step)(self.begin)
    forever = self.end is None
    index = 0
    while forever or result < self.end:
        yield result
        index += 1
        result = self.begin + self.step * index

如果一个类只是为了构建生成 器而去实现 __iter__ 方法,那还不如使用生成器函数。

def aritprog_gen(begin, step, end=None):
    result = type(begin + step)(begin)
    forever = end is None
    index = 0
    while forever or result < end:
        yield result
        index += 1
        result = begin + step * index

使用 itertools 模块生成等差数列

def aritprog_gen(begin, step, end=None):
    first = type(begin + step)(begin)
    ap_gen = itertools.count(first, step)
    if end is not None:
        ap_gen = itertools.takewhile(lambda n: n < end, ap_gen)
    return ap_gen

实现生成器时要知道标准库中有什么可用,否则很可能会重新发明轮子。

14.9 标准库中的生成器函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

yield from i完全代替了内层的for循环。i 是迭代器。

yield from还会创建通道,把内层生成器直接与外层生成器的客户端联系起来。把生成器当成协程使用时,这个通道特别重要,不仅能为客户端代码生成值,还能使用客户端代码提供的值。

14.11 可迭代的归约函数

all 和 any 函数会短路。

在这里插入图片描述

14.12 深入分析iter函数

在 Python 中迭代对象 x 时会调用 iter(x)

iter 函数还有一个鲜为人知的用法:传入两个参数,使用常规的函数或任何可调用的对象创建迭代器。这样使用时,第一个参数必须是可调用的对象,用于不断调用(没有参数),产出各个值;第二个值是哨符,这是个标记值,当可调用的对象返回这个值时,触发迭代器抛出 StopIteration 异常,而不产出哨符。

>>> def d6():
...     return randint(1, 6)
...
>>> d6_iter = iter(d6, 1)
>>> d6_iter
<callable_iterator object at 0x00000000029BE6A0>
>>> for roll in d6_iter:
...     print(roll)
...
4 3 6 3

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

14.14 把生成器当成协程

临时跳过。16 章讨论。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值