返回介绍

14.2 可迭代的对象与迭代器的对比

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

从 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:IterableIterator 抽象基类。以斜体显示的是抽象方法。具体的 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 技术交流群。

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

发布评论

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