- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
14.2 可迭代的对象与迭代器的对比
从 14.1.1 节的解说可以推知下述定义。
可迭代的对象
使用 iter 内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的 __iter__ 方法,那么对象就是可迭代的。序列都可以迭代;实现了 __getitem__ 方法,而且其参数是从零开始的索引,这种对象也可以迭代。
我们要明确可迭代的对象和迭代器之间的关系:Python 从可迭代的对象中获取迭代器。
下面是一个简单的 for 循环,迭代一个字符串。这里,字符串 'ABC' 是可迭代的对象。背后是有迭代器的,只不过我们看不到:
>>> s = 'ABC' >>> for char in s: ... print(char) ... A B C
如果没有 for 语句,不得不使用 while 循环模拟,要像下面这样写:
>>> s = 'ABC' >>> it = iter(s) # ➊ >>> while True: ... try: ... print(next(it)) # ➋ ... except StopIteration: # ➌ ... del it # ➍ ... break # ➎ ... A B C
❶ 使用可迭代的对象构建迭代器 it。
❷ 不断在迭代器上调用 next 函数,获取下一个字符。
❸ 如果没有字符了,迭代器会抛出 StopIteration 异常。
❹ 释放对 it 的引用,即废弃迭代器对象。
❺ 退出循环。
StopIteration 异常表明迭代器到头了。Python 语言内部会处理 for 循环和其他迭代上下文(如列表推导、元组拆包,等等)中的 StopIteration 异常。
标准的迭代器接口有两个方法。
__next__
返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常。
__iter__
返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。
这个接口在 collections.abc.Iterator 抽象基类中制定。这个类定义了 __next__ 抽象方法,而且继承自 Iterable 类;__iter__ 抽象方法则在 Iterable 类中定义。如图 14-1 所示。
图 14-1:Iterable 和 Iterator 抽象基类。以斜体显示的是抽象方法。具体的 Iterable.__iter__ 方法应该返回一个 Iterator 实例。具体的 Iterator 类必须实现 __next__ 方法。Iterator.__iter__ 方法直接返回实例本身
Iterator 抽象基类实现 __iter__ 方法的方式是返回实例本身(return self)。这样,在需要可迭代对象的地方可以使用迭代器。示例 14-3 是 abc.Iterator 类的源码。
示例 14-3 abc.Iterator 类,摘自 Lib/_collections_abc.py
class Iterator(Iterable): __slots__ = () @abstractmethod def __next__(self): 'Return the next item from the iterator. When exhausted, raise StopIteration' raise StopIteration def __iter__(self): return self @classmethod def __subclasshook__(cls, C): if cls is Iterator: if (any("__next__" in B.__dict__ for B in C.__mro__) and any("__iter__" in B.__dict__ for B in C.__mro__)): return True return NotImplemented
在 Python 3 中,Iterator 抽象基类定义的抽象方法是 it.__next__(),而在 Python 2 中是 it.next()。一如既往,我们应该避免直接调用特殊方法,使用 next(it) 即可,这个内置的函数在 Python 2 和 Python 3 中都能使用。
在 Python 3.4 中,Lib/types.py 模块的源码里有下面这段注释:
# Iterators in Python aren't a matter of type but of protocol. A large # and changing number of builtin types implement *some* flavor of # iterator. Don't check the type! Use hasattr to check for both # "__iter__" and "__next__" attributes instead.
其实,这就是 abc.Iterator 抽象基类中 __subclasshook__ 方法的作用(参见示例 14-3)。
考虑到 Lib/types.py 中的建议,以及 Lib/_collections_abc.py 中的实现逻辑,检查对象 x 是否为迭代器最好的方式是调用 isinstance(x, abc.Iterator)。得益于 Iterator.__subclasshook__ 方法,即使对象 x 所属的类不是 Iterator 类的真实子类或虚拟子类,也能这样检查。
再看示例 14-1 中定义的 Sentence 类,在 Python 控制台中能清楚地看出如何使用 iter(...) 函数构建迭代器,以及如何使用 next(...) 函数使用迭代器:
>>> s3 = Sentence('Pig and Pepper') # ➊ >>> it = iter(s3) # ➋ >>> it # doctest: +ELLIPSIS <iterator object at 0x...> >>> next(it) # ➌ 'Pig' >>> next(it) 'and' >>> next(it) 'Pepper' >>> next(it) # ➍ Traceback (most recent call last): ... StopIteration >>> list(it) # ➎ [] >>> list(iter(s3)) # ➏ ['Pig', 'and', 'Pepper']
❶ 创建一个 Sentence 实例 s3,包含 3 个单词。
❷ 从 s3 中获取迭代器。
❸ 调用 next(it),获取下一个单词。
❹ 没有单词了,因此迭代器抛出 StopIteration 异常。
❺ 到头后,迭代器没用了。
❻ 如果想再次迭代,要重新构建迭代器。
因为迭代器只需 __next__ 和 __iter__ 两个方法,所以除了调用 next() 方法,以及捕获 StopIteration 异常之外,没有办法检查是否还有遗留的元素。此外,也没有办法“还原”迭代器。如果想再次迭代,那就要调用 iter(...),传入之前构建迭代器的可迭代对象。传入迭代器本身没用,因为前面说过 Iterator.__iter__ 方法的实现方式是返回实例本身,所以传入迭代器无法还原已经耗尽的迭代器。
根据本节的内容,可以得出迭代器的定义如下。
迭代器
迭代器是这样的对象:实现了无参数的 __next__ 方法,返回序列中的下一个元素;如果没有元素了,那么抛出 StopIteration 异常。Python 中的迭代器还实现了 __iter__ 方法,因此迭代器也可以迭代。
因为内置的 iter(...) 函数会对序列做特殊处理,所以第 1 版 Sentence 类可以迭代。接下来要实现标准的可迭代协议。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论