本篇文章简单谈谈可迭代对象,迭代器和生成器之间的关系。
首先用一张图直观的感受下三者以及序列、字典简单关系。
序列与迭代
从关系图中我们可以看出序列也是可迭代对象,但是在迭代的分类上序列与迭代器是一个级别的,所以序列实现迭代的方式一定是区别于迭代器的,下面讲述原因。
1 | import re |
迭代对象时的步骤:
- 调用内置的
iter()
函数。 - 检查对象是否实现了
__iter__
方法,如果实现了则调用获取一个迭代器。 - 如果没有实现
__iter__
方法,则会检查是否实现了__getitem__
方法,如果实现了该方法并且索引是从0开始的,Python会自动创建一个迭代器。 - 如果二者都没有实现,Python会抛出TypeError异常。
鸭子与白鹅
- 鸭子:实现
__iter__
与__getitem__
两个方法,并且后者的参数从0开始,才认为是可以迭代的。 - 白鹅:实现
__iter__
方法就是可以迭代的。可以通过issubclass(C, abc.Iterable)
的测试。
可迭代对象与迭代器
关系
- 可迭代对象包含迭代器。
- 如果一个对象拥有
__iter__
方法,其是可迭代对象,如果一个对象拥有__next__
方法,其是迭代器。 - 可迭代对象必须实现
__iter__
方法,迭代器必须实现__iter__
和__next__
方法。
两个抽象基类
Iterable.__iter__
方法返回一个Iterator
实例。Iterator.__iter__
方法返回实例本身,并且具体的Iterator
类必须实现__next__
方法。
得益于Iterator.__subclasshook__
方法,我们可以使用issubclass(C, abc.Iterable)
检查对象C是否为迭代器,这样即使对象C所属的类不是Iterator
的真实子类或虚拟子类也可以检查。
__next__
__next__
方法不接受参数,所以只能执行一种操作–取下一个元素(当没有下一个元素是抛出StopIteration异常)。__next__
的特点导致了迭代器是一次性用品,无法还原。next()
函数调用__next__
方法。
自定义迭代器
1 | #!/usr/bin/env python |
Sentence
是一个可迭代对象(类)(实现了__iter__
方法),SentenceIterator
是一个迭代器(实现了__iter__
和__next__
方法)。
迭代器模式的作用:
- 访问一个聚合对象的内容而无需暴露它的内部表示
- 支持对聚合对象的多种遍历
- 为了遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)
迭代器模式的要求:
为了“支持多种遍历”,必须能从同一个可迭代的实力中获取多个独立的迭代器。而且每个迭代器要能维护自身的内部状态,所以这一模式的正确实现方式是,每次调用iter(C)
都新建一个独立的迭代器。所以上述代码中Sentence.__iter__
方法返回一个迭代器SentenceIterator
,也正印证了这里所说。
可迭代对象一定不能是自身的迭代器(迭代器的__iter__
方法要返回自己,这样就没法维持对象自身状态不变)。所以可迭代对象必须实现__iter__
方法,但是不能实现__next__
方法。
生成器
1 | import re |
生成器函数
1 | def aritprog_gen(begin, step, end = None): |
只要在Python函数的定义体中有yield
关键字,该函数就是生成器函数,调用生成器函数时,会返回一个生成器对象,即生成器函数是生成器工厂。
惰性求值与及早求值
生成器表达式
生成器表达式是语法糖完全可以代替生成器函数,而且有时使用生成器表达式更便利。
1 | import re |
生成器作为协程使用
16章讲,作者特意分开了。
yield from
在生成器函数中,yield from
可以代替一层for循环用于产生元素。另外yield from
函数可以创建通道,把内层生成器直接与外层生成器的客户端联系起来,把生成器当作协程使用。
1 | def chain(*iterables): |
iter()
函数
可以传入两个参数,使用常规的函数或任何可调用的对象创建迭代器,其中第一个参数(可调用对象)在没有参数时可以不断被调用并产生新的值。第二个参数是哨符,当可调用的对象返回这个值时,出发迭代器抛出StopIteration
异常,而不产出哨符。
1 | from random import randint |
标准库中的生成器函数
- 用于过滤的生成器函数
- 用于映射的生成器函数
- 合并多个可迭代对象的生成器函数
- 把输入的各个元素扩展成多个输出的生成器函数
- 用于重新排列元素的生成器函数
- 可迭代的归约函数
把生成器当成协程
与__next()__
方法一样.send()
方法致使生成器进入下一个yield
语句,但是.send()
方法允许使用生成器的客户端把数据传给自己,实现数据的双向交换。