购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.5 迭代器协议

本节详细解释Python迭代器协议,让读者从底层深入理解生成器、迭代器和可迭代对象的工作原理。对于大多数程序员的日常工作,本节的重要性不如其他节。不过,你需要了解这些信息,以实现自定义的可迭代集合类型。就笔者而言,了解迭代器协议也有助于理解迭代相关的问题和特殊情况。通过掌握细节,可以快速排查并修复代码问题。

如果读者觉得本节内容很有价值,可继续阅读。如果不感兴趣,可以直接跳到下一章,以后随时阅读本节。

正如前文提到的,Python区分了迭代器和可迭代对象。区别不大,而且两个词听起来几乎相同。但读者要牢记,迭代器和可迭代对象是不同但相关的概念,下面的内容才会更容易理解。

简单而言,迭代器是可以传递给next()函数,或在for循环中仅使用一次的对象。更正式地,Python对象如果遵循迭代器协议,则称为迭代器。如果对象需满足以下条件,就称为遵循迭代器协议:

●定义有__next__()方法,该方法不接收任何参数。

●每次调用__next__()时,对象会获取序列中的下一个元素,直到生成所有序列项。

●然后,再调用__next__()会抛出StopIteration异常。

●该对象还定义了__iter__()方法,该方法不接收任何参数,而是返回相同的迭代器。__iter__()方法的主体是return self。

任何具有这些方法的对象都可以称为Python迭代器。用户无须调用__next__()方法,而是使用内置的next()函数。

为了更好地理解,下面是next()的可能编写方法:

(注意,此函数创建了专用的哨兵值_NO_DEFAULT,而不是使用像None这样的内置值。哨兵值主要用于算法中的信号传递,不会与真实数据的任何值冲突。这样,向default传递任何值也不会发生冲突。)

以上内容解释了迭代器,接下来解释可迭代对象。一般而言,如果对象可用于for循环,则该对象就是可迭代的。更正式地,如果Python对象满足以下两个条件之一,它就是可迭代的:

●该对象定义了__iter__()方法,该方法创建并返回一个用于迭代容器中元素的迭代器;

●该对象定义了__getitem__()方法,可用于方括号的方法,引用foo[0]、foo[1]等。如果超出最后一个元素范围,则抛出IndexError异常。

(注意,iterator是一个名词,而iterable通常用作形容词,有助于记住二者的区别。)

当实现容器类型时,你可能希望让该容器可迭代,以用于for循环。根据容器的性质,通常最容易实现的是可迭代协议。作为示例,考虑UniqueList类型,这是一种介于列表和集合的混合类型:

用法如下:

__getitem__()方法实现了方括号访问,Python会将u[3]转换为u.__getitem__(3)。对象使用方括号后,可使操作类似普通列表,即起始元素位于索引0,通过整数获取后续元素,不会跳过任何元素。当超过末尾时,会引发IndexError。如果对象具有如此工作的__getitem__()方法,iter()就知道如何将该对象创建为迭代器:

注意,实现很简单是因为代码内部使用了列表(负责处理__getitem__逻辑)。当创建类似列表的自定义集合,或其他标准集合类型时,这是一个编码技巧。如果对象内部以标准数据类型存储数据,则模仿其行为通常会更容易一些。

有时,更简洁的方法是继承可迭代对象。另一种(也许更好的 )实现UniqueList的方法是继承list:

然后,就可以像处理普通Python列表一样来使用UniqueList,并充分利用可迭代特性。

编写类似列表的__getitem__()方法,是一种使Python类支持迭代的方法(还可以添加__len__()方法)。另一种方法是编写__iter__()方法。当不带参数调用时,__iter__()必须返回遵循迭代器协议的对象。在最坏的情况下,你需要实现类似于前文提到的SquaresIterator类,该类具有__next__()和__iter__()方法。通常情况下,不需要这么麻烦,返回生成器对象就可以了。这意味着__iter__()本身就是生成器函数,或者__iter__()内部调用其他生成器函数,并返回值。

迭代器必须有__iter__()方法,某些可迭代对象也要有__iter__()方法。调用迭代器和可迭代对象都不带参数,且都返回迭代器对象。两者唯一不同的是,迭代器的__iter__()返回self,而可迭代对象的__iter__()则创建并返回新的迭代器。如果调用两次__iter__(),则得到两个不同的迭代器。

这种相似性是故意的,目的是简化能够接收迭代器或可迭代对象的控制代码。你可以安全地遵循以下模式:当Python运行时遇到for循环时,会首先调用iter(sequence),且总是返回迭代器:可能是sequence本身,或(如果sequence仅仅是可迭代对象)是通过sequence.__iter__()创建的迭代器。

可迭代对象在Python中无处不在。几乎所有的内置集合类型都是可迭代的,包括list(列表)、tuple(元组)和set(集合),甚至dict(字典)也是可迭代的。读者可以尝试dict.items(),for x in some_dict则能迭代字典中的键。

在自定义的集合类中,有时实现__iter__()的最简单方法是使用iter()。例如,以下方法存在问题:

运行代码,会得到TypeError异常:

问题出在__iter__()方法应该返回迭代器,但列表对象并不是迭代器,而是可迭代对象。只需修改一行代码,使用iter(),而不是__iter__(),创建迭代器对象并返回,如下所示:

这样,类WorksInLoops就成了可迭代对象,因为__iter__()方法返回的是真正的迭代器对象,可确保WorksInLoops遵循迭代器协议。每次调用该__iter__()方法,都会生成新的迭代器: qtGFA9OIfjuouGTp7PxT9uC54N+RADCJUv8X+uEKsRSrGQ/E2FH4eo3PG+liQvWD

点击中间区域
呼出菜单
上一章
目录
下一章
×