给定一个集合,循环对集合中的每个元素应用某个操作,称之为迭代。
python迭代器
题目:给定一个序列,循环输出序列中的每个元素。
通过一个for循环就可以实现,如下:
for循环遍历
以上demo中,我们使用了print函数的双参数形式,通过第二个参数end,指定以一个空格作为结尾,以代替原来的换行。控制台结果如下:
for循环遍历结果
事实上,python中,提供了另一个工具来帮助遍历,该工具称之为迭代器。可以通过iter函数来获取一个迭代器,通过next函数来获取下一个元素。如下:
迭代器
上面的demo里,通过iter函数获取到了一个迭代器,再通过调用和序列元素个数相同次数的next函数,来不断通过迭代器取出序列中的元素。这里,也展示了next方法的特性,即每次调用,都会向后移一位。使得下次取出的是下一个元素。
demo里,通过序列元素的个数,限制了next函数调用的次数。假设没有限制,那么就会可能发生next不断后移,超出序列元素个数的情况,此时会发生什么呢?我们用如下代码测试:
无限next
得到如下结果:
StopIteration
我们看到,在控制台上,得到了一个异常,StopIteration,即停止迭代。
事实上,不需要range和len函数的帮助,可以直接通过for来对一个迭代器进行遍历,就像遍历原来的序列一样,如下:
for循环遍历迭代器
python迭代器的意义
有同学可能会有疑问:既然通过for循环就可以遍历一个序列,为什么又要来一个迭代器呢?
事实上,for循环能够正常运行的基础,就是因为相应的类实现了迭代器。
换句话说,假设有一个我们自己定义类,想让for循环可以作用于该类的对象,就需要为该类实现迭代器。一种实现方式是提供两个方法,分别是__iter__和__next__。
假设我们定义一个类A,并不提供__iter__和__next__方法,然后创建一个A类的对象a,尝试使用for循环对a进行遍历。如下:
遍历1
结果1
结果是我们得到了一个TypeError,类型错误。
我们尝试在类A中添加__iter__和__next__方法,如下:
遍历2
我们在控制台得到了不断输出的1。如下:
结果2
由以上demo可知,for语句的本质其实是不断对next的调用,其中的x,就是next的返回值。那么,是不是__iter__方法不重要呢?事实上,当将__iter__方法删除后,还是会得到一个TypeError,这说明,__iter__方法是重要的。
demo里,__next__方法简单返回了一个1,事实上,在真正使用时,这个方法可能需要更加丰富的逻辑。比如说,返回一个从1开始不断递增的数字序列,并且当大于10时,停止循环 。代码如下:
遍历3
结果如下:
结果3
上面的demo中,使用了类定义,对象创建,类变量,类方法,异常抛出等内容,如果有不理解的同学,我们后面的文章会聊到。
python生成器
前面的demo里,通过给一个类添加__iter__和__next__方法,使得该类的对象可以通过for语句来遍历。那么,假设我们有一个test方法,我们希望test方法的返回值可以被for语句遍历,该怎么写?比如下面这样:
错误的方法返回值遍历
这种方案显然是不行的,运行时会报TypeError。但是,比如说,我们直接返回一个序列类型的对象,如下:
返回序列
这种方案显然是可以的,但是,这显得有些投机取巧,且不具有通用性。python提供了一种生成器的机制,可以使方法返回值为一个迭代器,我们称之为生成器,即生成迭代器的工具。通过yield语句来实现,如下:
yield生成器
运行代码,发现可以正常运行,控制台输出了1。可以验证,yield确实可以生成一个迭代器并返回。那么,如何利用它来实现更丰富的功能呢?我们尝试实现一个递增的数字序列,返回1到10。如下:
生成器实现递增序列
运行程序,发现正确得到了从1到10的递增序列。
yield语句的作用是,当执行到yield时,会保存现场(各个变量的值),并返回一个迭代器。当再次执行时,yield前面的代码将不会再次执行,将直接从yield语句后面的代码开始执行。在上面的demo里,由于我们用了一个while True无限循环,所以,每次当a += 1执行完后,会再次循环执行到yield a。直接 a > 10。
前面一篇文章里我们聊到了斐波那契数列的demo,当时一个用for循环实现的代码如下:
斐波那契数列1
demo里,我们在for语句块的上面,定义了三个变量,分别是lastOne,lastTwo和current,这样做的原因是为了保存每次循环后,lastOne和lastTwo的结果。
利用生成器,我们可以以下面的方式得到斐波那契数列。代码如下:
斐波那契数列2
观察上面的代码,test是一个方法,它是可以接收参数的,中间变量的声明也可以简化,while循环也可以优化,最终我们可以得到一个更简洁且更具有通用性的代码:
斐波那契数列3
显然,fbnq方法可以接收一个参数,该参数可以控制输出序列前n项。