- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- 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.1 Sentence 类第1版:单词序列
我们要实现一个 Sentence 类,以此打开探索可迭代对象的旅程。我们向这个类的构造方法传入包含一些文本的字符串,然后可以逐个单词迭代。第 1 版要实现序列协议,这个类的对象可以迭代,因为所有序列都可以迭代——这一点前面已经说过,不过现在要说明真正的原因。
示例 14-1 定义了一个 Sentence 类,通过索引从文本中提取单词。
示例 14-1 sentence.py:把句子划分为单词序列
import re import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text): self.text = text self.words = RE_WORD.findall(text) ➊ def __getitem__(self, index): return self.words[index] ➋ def __len__(self): ➌ return len(self.words) def __repr__(self): return 'Sentence(%s)' % reprlib.repr(self.text) ➍
❶ re.findall 函数返回一个字符串列表,里面的元素是正则表达式的全部非重叠匹配。
❷ self.words 中保存的是 .findall 函数返回的结果,因此直接返回指定索引位上的单词。
❸ 为了完善序列协议,我们实现了 __len__ 方法;不过,为了让对象可以迭代,没必要实现这个方法。
❹ reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表示形式。4
4首次使用 reprlib 模块是在 10.2 节。
默认情况下,reprlib.repr 函数生成的字符串最多有 30 个字符。Sentence 类的用法参见示例 14-2 中的控制台会话。
示例 14-2 测试 Sentence 实例能否迭代
>>> s = Sentence('"The time has come," the Walrus said,') # ➊ >>> s Sentence('"The time ha... Walrus said,') # ➋ >>> for word in s: # ➌ ... print(word) The time has come the Walrus said >>> list(s) # ➍ ['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']
❶ 传入一个字符串,创建一个 Sentence 实例。
❷ 注意,__repr__ 方法的输出中包含 reprlib.repr 方法生成的 ...。
❸ Sentence 实例可以迭代,稍后说明原因。
❹ 因为可以迭代,所以 Sentence 对象可以用于构建列表和其他可迭代的类型。
在接下来的几页中,我们还要开发其他 Sentence 类,而且都能通过示例 14-2 中的测试。不过,示例 14-1 中的实现与其他实现都不同,因为这一版 Sentence 类也是序列,可以按索引获取单词:
>>> s[0] 'The' >>> s[5] 'Walrus' >>> s[-1] 'said'
所有 Python 程序员都知道,序列可以迭代。下面说明具体的原因。
序列可以迭代的原因:iter函数
解释器需要迭代对象 x 时,会自动调用 iter(x)。
内置的 iter 函数有以下作用。
(1) 检查对象是否实现了 __iter__ 方法,如果实现了就调用它,获取一个迭代器。
(2) 如果没有实现 __iter__ 方法,但是实现了 __getitem__ 方法,Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素。
(3) 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C object is not iterable”(C 对象不可迭代),其中 C 是目标对象所属的类。
任何 Python 序列都可迭代的原因是,它们都实现了 __getitem__ 方法。其实,标准的序列也都实现了 __iter__ 方法,因此你也应该这么做。之所以对 __getitem__ 方法做特殊处理,是为了向后兼容,而未来可能不会再这么做(不过,写作本书时还未弃用)。
11.2 节提到过,这是鸭子类型(duck typing)的极端形式:不仅要实现特殊的 __iter__ 方法,还要实现 __getitem__ 方法,而且 __getitem__ 方法的参数是从 0 开始的整数(int),这样才认为对象是可迭代的。
在白鹅类型(goose-typing)理论中,可迭代对象的定义简单一些,不过没那么灵活:如果实现了 __iter__ 方法,那么就认为对象是可迭代的。此时,不需要创建子类,也不用注册,因为 abc.Iterable 类实现了 __subclasshook__ 方法,如 11.10 节所述。下面举个例子:
>>> class Foo: ... def __iter__(self): ... pass ... >>> from collections import abc >>> issubclass(Foo, abc.Iterable) True >>> f = Foo() >>> isinstance(f, abc.Iterable) True
不过要注意,虽然前面定义的 Sentence 类是可以迭代的,但却无法通过 issubclass (Sentence, abc.Iterable) 测试。
从 Python 3.4 开始,检查对象 x 能否迭代,最准确的方法是:调用 iter(x) 函数,如果不可迭代,再处理 TypeError 异常。这比使用 isinstance(x, abc.Iterable) 更准确,因为 iter(x) 函数会考虑到遗留的 __getitem__ 方法,而 abc.Iterable 类则不考虑。
迭代对象之前显式检查对象是否可迭代或许没必要,毕竟尝试迭代不可迭代的对象时,Python 抛出的异常信息很明确:TypeError: 'C' object is not iterable。如果除了抛出 TypeError 异常之外还要做进一步的处理,可以使用 try/except 块,而无需显式检查。如果要保存对象,等以后再迭代,或许可以显式检查,因为这种情况可能需要尽早捕获错误。
下一节详述可迭代的对象和迭代器之间的关系。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论