“迭代”是程序设计中的一个非常重要概念,它可以简单地理解为“重复地做一些事情”。Python提供了迭代器和生成器的功能,但这些概念不易掌握。这篇文章主要是总结下我个人的一些理解。不对之处,恳请各位批评指正。下面所有代码的运行环境为Python 3.6.8。
一. 迭代的必要性
Python程序设计中,我们为什么要学习迭代?首先,采用迭代可以方便地遍历集合类对象(list、tuple、range、set、dict等)。其次,迭代采用延迟计算,可以降低存储空间的要求。
Python的循环只有while 和 for...in...两种结构。通过for......in结构可以方便地遍历集合类对象中的所有元素。如下所示
Python中的for...in...结构中必须包含可迭代对象,其运行机制稍后会讲到。
下面再看一个比较极端一点的例子,假如说,我程序中需要用到0~100000000的平方。直观的方案是分别计算这100000000个数的平方并按序存储在一个列表中。毫无疑问,这会造成巨大的内存开销。另一种方案是采用生成器,延迟计算。也就是说,我们并不提前计算和保存每个数的平方,而只是保存生成下一个数平方的算法。只有真正用到时,我们才调用算法进行计算。这种方案显著降低了内存开销。如下图所示:
二. 可迭代对象
1. 可迭代对象的概念
Iterable类型的对象称为可迭代对象。Python通过isinstance(p, Iterable),来判断一个对象p是否为可迭代对象。那么什么样的对象才是可迭代对象,一句话概括就是“凡是实现了__iter__方法的对象就是可迭代对象”。
Python中的list、tuple、set、dict、range等集合类的对象都是可迭代对象。如下例所示,我们首先定义了一个列表对象l。通过dir可查看l所有的成员变量和成员方法,可以发现l实现了__iter__方法。isinstance(l,Iterable)结果为True,即l是一个可迭代对象。
再举一个例子。我们首先定义一个Student类,其中实现了__iter__方法(空函数)。然后,生成Student类的一个对象Jack。Jack测试为可迭代对象。
2. 可迭代对象的使用
可迭代对象,常用于for...in...结构,用于遍历所有数据,也用于高阶函数map/reduce/filter等高阶函数中。
三. 迭代器对象
1. 迭代器对象的概念
Iterator类型的对象称为迭代器对象。Python中通过isinstance(p, Iterator),来判断一个对象p是否为迭代器对象。那么什么样的对象才是可迭代对象呢?一句话概括就是“凡是实现了__iter__方法和__next__方法的对象就是迭代器对象”。通过比较第二部可迭代对象的概念,可以发现:迭代器对象一定是可迭代对象;反之,则未必。
Python中的list、tuple、set、dict、range等集合类对象未实现__next__方法。因此,它们都不是迭代器对象。如下所示:
但是,调用对象l和d的__iter__函数,则会返回一个迭代器对象。至于__iter__函数具体是如何实现的,我们这里并不关心。
为了进一步说明这个问题,我们自定义A,B,C三个类。其中,A类只实现了__iter__方法,B类只实现了__next__方法,C类同时实现了__iter__方法和__next__方法。然后,我们分别实例化这三个类的对象a,b,c。经测试发现,a为可迭代对象,但不是迭代器对象;b既不是可迭代对象,也不是迭代器对象;c是可迭代对象,也是迭代器对象。如下所示:
2. 迭代器对象的使用
迭代器对象,可以调用__next__函数,不断返回下一个数据,直到没有数据时抛出StopIteration
错误。
四. 生成器对象
1. 生成器对象的概念
生成器对象,顾名思义就是生成器(generator)类型的对象。可以通过type(p)查看对象p是否为生成器对象。Python中提供了两种方法来构造生成器对象。
一种方法是采用生成器表达式。这只需要讲列表生成式中的"[ ]"替换为“()”,如下所示:
另外一种方法是采用带yield关键字的生成器函数。如下所示:
2. 生成器对象的使用
生成器对象中(隐含)实现了__iter__和__next__方法,因而也是迭代器对象。可以通过调用__next__函数返回下一个数据。采用带yield的函数创建的生成器对象,在每次调用__next__函数时开始执行,遇到yield
语句就返回。再次调用时,从上次返回的yield
语句处继续执行。如下所示:
五. 可迭代对象、迭代器对象和生成器对象的关系
生成器对象必定是迭代器对象,迭代器对象又必定是可迭代对象。三者之间存在一种包含关系,如下图所示:
六. for ... in ... 结构的工作过程
可迭代对象常用在for...in...结构中,其工作过程是:首先会调用可迭代对象的__iter__方法,应返回一个迭代器对象。然后,for循环会不断调用所返回迭代器对象的__next__方法。直到遇到StopIteration错误时退出循环。如下所示: