返回介绍

14.1 Sentence 类第1版:单词序列

发布于 2024-02-05 21:59:47 字数 3472 浏览 0 评论 0 收藏 0

我们要实现一个 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文