流畅的Python 可迭代的对象、迭代器和生成器
单词序列Sentence
1 | import re |
序列可以迭代的原因:iter函数
解释器需要迭代对象 x 时,会自动调用 iter(x)。
内置的 iter 函数有以下作用:
- 检查对象是否实现了
__iter__方法,如果实现了就调用它,获取一个迭代器。 - 如果没有实现
__iter__方法,但是实现了__getitem__方法,Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素。 - 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C object is not iterable”(C对象不可迭代),其中 C 是目标对象所属的类。
任何 Python 序列都可迭代的原因是,它们都实现了 __getitem__ 方法。其实,标准的序列也都实现了 __iter__ 方法,因此也应该这么做。
检查一个对象是否可迭代:
1 | from collections import abc |
检查对象 x 能否迭代,最准确的方法是:调用 iter(x) 函数,如果不可迭代,再处理TypeError 异常。 这比使用 isinstance(x, abc.Iterable) 更准确,因为 iter(x) 函数会考虑到遗留的 __getitem__ 方法,而 abc.Iterable 类则不考虑。
可迭代的对象与迭代器的对比
可迭代的对象:
使用 iter 内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的 __iter__方法,那么对象就是可迭代的。序列都可以迭代;另外,实现了 __getitem__ 方法,而且其参数是从零开始的索引,这种对象也可以迭代。
可迭代的对象和迭代器之间的关系:Python 从可迭代的对象中获取迭代器。
字符串 ‘abc’ 是可迭代的对象。背后是有迭代器的,只不过我们看不到:
1 | s = "abc" |
如果没有 for 语句,不得不使用 while 循环模拟:
1 | s_iter = iter(s) # 使用可迭代的对象构建迭代器 s_iter。 |
StopIteration 异常表明迭代器到头了。Python 语言内部会处理 for 循环和其他迭代上下文(如列表推导、元组拆包,等等)中的 StopIteration 异常。
标准的迭代器接口有两个方法:
__next__
返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常。__iter__
返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。
这个接口在 collections.abc.Iterator 抽象基类中制定。这个类定义了 __next__ 抽象方法,而且继承自 Iterable 类;__iter__ 抽象方法则在 Iterable 类中定义。

图解:Iterable 和 Iterator 抽象基类。以斜体显示的是抽象方法。具体的 Iterable.__iter__ 方法应该返回一个 Iterator 实例。具体的 Iterator 类必须实现 __next__ 方法。Iterator.__iter__ 方法直接返回实例本身。
Iterator 抽象基类实现 __iter__ 方法的方式是返回实例本身(return self)。这样,在需要可迭代对象的地方可以使用迭代器。 abc.Iterator 类的源码:
1 | # from Lib._collections_abc import Iterator |
检查对象 x 是否为迭代器最好的方式是调用 isinstance(x, abc.Iterator),相应的,检查是都是迭代对象用 isinstance(x, abc.Iterable)。
1 | from collections import abc |
迭代器只需 __next__ 和 __iter__ 两个方法,所以除了调用 next() 方法,以及捕获 StopIteration 异常之外,没有办法检查是否还有遗留的元素。此外,也没有办法“还原”迭代器。如果想再次迭代,那就要调用 iter(…),传入之前构建迭代器的可迭代对象。传入迭代器本身没用,因为前面说过 Iterator.__iter__ 方法的实现方式是返回实例本身,所以传入迭代器无法还原已经耗尽的迭代器。
1 | s1 = Sentence("I am zyp") |
迭代器
迭代器是这样的对象:实现了无参数的 __next__ 方法,返回序列中的下一个元素;如果没有元素了,那么抛出 StopIteration 异常。Python 中的迭代器还实现了 __iter__ 方法,因此迭代器也可以迭代。