8.11 迭代器和iter()函数
8.11.1 什么是迭代器
迭代器是在版本2.2被加入Python的,它为类序列对象提供了一个类序列的接口。我们在前边的第6章已经正式地介绍过序列。它们是一组数据结构,你可以利用它们的索引从0开始一直“迭代”到序列的最后一个条目。用“计数”的方法迭代序列是很简单的。Python的迭代无缝地支持序列对象,而且它还允许程序员迭代非序列类型,包括用户定义的对象。
迭代器用起来很灵巧,你可以迭代不是序列但表现出序列行为的对象,例如字典的键、一个文件的行,等等.当你使用循环迭代一个对象条目时,你几乎分辨不出它是迭代器还是序列。你不必去关注这些,因为Python让它像一个序列那样操作。
8.11.2 为什么要迭代器
引用PEP(234)中对迭代器的定义:
提供了可扩展的迭代器接口;
对列表迭代带来了性能上的增强;
在字典迭代中性能提升;
创建真正的迭代接口,而不是原来的随机对象访问;
与所有已经存在的用户定义的类以及扩展的模拟序列和映射的对象向后兼容;
迭代非序列集合(例如映射和文件)时,可以创建更简洁可读的代码。
8.11.3 如何迭代
根本上说,迭代器就是有一个next()方法的对象,而不是通过索引来计数。当你或是一个循环机制(例如for语句)需要下一个项时,调用迭代器的next()方法就可以获得它。条目全部取出后,会引发一个StopIteration异常,这并不表示错误发生,只是告诉外部调用者,迭代完成。
不过,迭代器也有一些限制。例如你不能向后移动,不能回到开始,也不能复制一个迭代器。如果你要再次(或者是同时)迭代同个对象,你只能去创建另一个迭代器对象。不过,这并不糟糕,因为还有其他的工具来帮助你使用迭代器。
reversed()内建函数将返回一个反序访问的迭代器。enumerate()内建函数同样也返回迭代器。另外两个新的内建函数,any()和all(),是在Python 2.5中新增的,如果迭代器中某个/所有条目的值都为布尔真时,则它们返回值为真。本章先前部分我们展示了如何在for循环中通过索引或是可迭代对象来遍历条目。同时Python还提供了一整个itertools模块,它包含各种有用的迭代器。
8.11.4 使用迭代器
1. 序列
正如先前提到的,迭代Python的序列对象和你想像的一样:
如果这是一个实际应用程序,那么我们需要把代码放在一个try-except块中。序列现在会自动地产生它们自己的迭代器,所以一个for循环:
under the covers now really behaves like this:
实际上是这样工作的:
不过,你不需要改动你的代码,因为for循环会自动调用迭代器的next()方法(以及监视 Stoplteration异常)。
2. 字典
字典和文件是另外两个可迭代的Python数据类型。字典的迭代器会遍历它的键(key)。语句for eachKey in myDict.keys()可以缩写为for eachKey in myDict,例如:
另外,Python还引进了三个新的内建字典方法来定义迭代:myDict.iterkeys()(通过键迭代)、 myDict.itervalues()(通过值迭代)及myDicit.iteritems()(通过键-值对来迭代)。注意,in操作符也可以用于检查字典的键是否存在,之前的布尔表达式myDict.has_key(anyKey)可以被简写为anyKey in myDict。
3. 文件
文件对象生成的迭代器会自动调用readline()方法。这样,循环就可以访问文本文件的所有行。程序员可以使用更简单的for eachLine in myFile替换for eachLine in myFile.readlines():
8.11.5 可变对象和迭代器
记住,在迭代可变对象的时候修改它们并不是个好主意。这在迭代器出现之前就是一个问题。一个流行的例子就是循环列表的时候删除满足(或不满足)特定条件的项:
除列表外的其他序列都是不可变的,所以危险就发生在这里。一个序列的迭代器只是记录你当前到达第多少个元素,所以如果你在迭代时改变了元素,更新会立即反映到你所迭代的条目上。在迭代字典的键时,你绝对不能改变这个字典。使用字典的keys()方法是可以的,因为keys()返回一个独立于字典的列表。而迭代器是与实际对象绑定在一起的,它将不会继续执行下去:
这样可以避免有缺陷的代码。更多有关迭代器的细节请参阅PEP 234。
8.11.6 如何创建迭代器
对一个对象调用iter()就可以得到它的迭代器。它的语法如下:
如果你传递一个参数给iter(),它会检查你传递的是不是一个序列,如果是,那么很简单:根据索引从0一直迭代到序列结束。另一个创建迭代器的方法是使用类,我们将在第13章详细介绍,一个实现了iter()和next()方法的类可以作为迭代器使用。
如果是传递两个参数给iter(),它会重复地调用func,直到迭代器的下个值等于sentinel。