返回介绍

14.3 Sentence 类第2版:典型的迭代器

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

第 2 版 Sentence 类根据《设计模式:可复用面向对象软件的基础》一书给出的模型,实现典型的迭代器设计模式。注意,这不符合 Python 的习惯做法,后面重构时会说明原因。不过,通过这一版能明确可迭代的集合和迭代器对象之间的关系。

示例 14-4 中定义的 Sentence 类可以迭代,因为它实现了特殊的 __iter__ 方法,构建并返回一个 SentenceIterator 实例。《设计模式:可复用面向对象软件的基础》一书就是这样描述迭代器设计模式的。

这里之所以这么做,是为了清楚地说明可迭代的对象和迭代器之间的重要区别,以及二者之间的联系。

示例 14-4 sentence_iter.py:使用迭代器模式实现 Sentence 类

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 __repr__(self):
    return 'Sentence(%s)' % reprlib.repr(self.text)

  def __iter__(self):  ➊
    return SentenceIterator(self.words)  ➋


class SentenceIterator:

  def __init__(self, words):
    self.words = words  ➌
    self.index = 0  ➍

  def __next__(self):
    try:
      word = self.words[self.index]  ➎
    except IndexError:
      raise StopIteration()  ➏
    self.index += 1  ➐
    return word  ➑

  def __iter__(self):  ➒
    return self

❶ 与前一版相比,这里只多了一个 __iter__ 方法。这一版没有 __getitem__ 方法,为的是明确表明这个类可以迭代,因为实现了 __iter__ 方法。

❷ 根据可迭代协议,__iter__ 方法实例化并返回一个迭代器。

❸ SentenceIterator 实例引用单词列表。

❹ self.index 用于确定下一个要获取的单词。

❺ 获取 self.index 索引位上的单词。

❻ 如果 self.index 索引位上没有单词,那么抛出 StopIteration 异常。

❼ 递增 self.index 的值。

❽ 返回单词。

❾ 实现 self.__iter__ 方法。

示例 14-4 中的代码能通过示例 14-2 中的测试。

注意,对这个示例来说,其实没必要在 SentenceIterator 类中实现 __iter__ 方法,不过这么做是对的,因为迭代器应该实现 __next__ 和 __iter__ 两个方法,而且这么做能让迭代器通过 issubclass(SentenceIterator, abc.Iterator) 测试。如果让 SentenceIterator 类继承 abc.Iterator 类,那么它会继承 abc.Iterator.__iter__ 这个具体方法。

这一版的工作量很大(对懒惰的 Python 程序员来说确实如此)。注意,SentenceIterator 类的大多数代码都在处理迭代器的内部状态。稍后会说明如何简化。不过,在此之前我们先稍微离题,讨论一个看似合理实则错误的实现捷径。

Sentence变成迭代器:坏主意

构建可迭代的对象和迭代器时经常会出现错误,原因是混淆了二者。要知道,可迭代的对象有个 __iter__ 方法,每次都实例化一个新的迭代器;而迭代器要实现 __next__ 方法,返回单个元素,此外还要实现 __iter__ 方法,返回迭代器本身。

因此,迭代器可以迭代,但是可迭代的对象不是迭代器。

除了 __iter__ 方法之外,你可能还想在 Sentence 类中实现 __next__ 方法,让 Sentence 实例既是可迭代的对象,也是自身的迭代器。可是,这种想法非常糟糕。根据有大量 Python 代码审查经验的 Alex Martelli 所说,这也是常见的反模式。

《设计模式:可复用面向对象软件的基础》一书讲解迭代器设计模式时,在“适用性”一节中说:5

5《设计模式:可复用面向对象软件的基础》第 172 页。

迭代器模式可用来:

访问一个聚合对象的内容而无需暴露它的内部表示

支持对聚合对象的多种遍历

为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)

为了“支持多种遍历”,必须能从同一个可迭代的实例中获取多个独立的迭代器,而且各个迭代器要能维护自身的内部状态,因此这一模式正确的实现方式是,每次调用 iter(my_iterable) 都新建一个独立的迭代器。这就是为什么这个示例需要定义 SentenceIterator 类。

可迭代的对象一定不能是自身的迭代器。也就是说,可迭代的对象必须实现 __iter__ 方法,但不能实现 __next__ 方法。

另一方面,迭代器应该一直可以迭代。迭代器的 __iter__ 方法应该返回自身。

至此,我们演示了如何正确地实现典型的迭代器模式。本节至此告一段落,下一节展示如何使用更符合 Python 习惯的方式实现 Sentence 类。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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

发布评论

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