搬砖的也能玩Python——进阶篇
1-迭代器
回顾
在讲循环语句的基础篇5中提到,for循环会访问一个可迭代对象,并且遍历可迭代对象中的每一个元素,那么具体的原理是什么呢?我们的进阶篇第一篇文章,就带大家来了解一下“迭代器”的概念。
一、迭代器的相关概念
迭代器
迭代器(iterator)就是一个实现了迭代器协议的对象,这个对象包含下面两个方法:
__next__():获取容器中的下一个对象。
__iter__():获取迭代器对象。
每次使用__next__()方法便会得到下一个元素,当所有的元素都获取完之后,便会引发一个StopIteration异常。我们的for循环就是先调用__next__()方法来遍历每一个元素,当for循环捕获StopIteration异常之后,循环就自动停止了。
在Python中,可以用iter()方法来创建一个迭代器,我们来看一个简单的例子:
在上图的例子中,我们用iter()方法,将一个列表变成了一个装饰器,并赋值给了变量i,针对i,我们每次使用next()方法,便会依次得到迭代器中的元素。当我们3个元素都的出来之后,又一次执行了next()方法,我们来看看结果。
结果会报出StopIteration错误。
迭代器对象
在Python中,对于实现了__next__()方法的对象,同时__iter__()方法返回对象本身,我们称之为迭代器对象。
可迭代对象
在Python中,对于实现了__iter__()方法的对象,我们称之为可迭代对象,可迭代对象中的__iter__()方法,返回了一个迭代器对象。
接下来,我们一步一步的了解迭代器对象和可迭代对象。
二、迭代器对象
想要自定义一个迭代器,只需要编写一个具有__next__()方法的类,并且__iter__()方法返回迭代器实例即可。我们来看下面这个例子:
在上图的例子中,我们定义了一个列表迭代器的类ListIterator,并且实现了__iter__()方法和__next__()方法,__iter__()方法返回了迭代器本身(self),__next__()方法我们先判断当前的索引是否超过了列表的长度,如果没有超过,就把列表当前索引的值输出,如果超过列表长度,就抛出StopIteration异常,这样就会使for循环结束。我们来看一下如何使用这个迭代器:
我们定义了一个实例nba,传进去了一个列表,在使用for循环的时候,我们写了一个pass语句,就是单纯把nba这个实例遍历一遍,并不执行任何的操作,我们来看一下结果是怎么样的。
从结果中我们看出,执行for循环时,先寻找并执行__iter__()方法,由于存在__iter__()方法,所以我们的nba实例是可迭代的,接下来for循环会自动调用__next__()方法,执行我们__next__()方法下的内容,直到最后一次执行时,捕获到了StopIteration异常,for循环结束。
其实我们还可以用while循环,来模拟一下for循环的实现过程:
从上图中看到,我们把while循环写成一个无限循环,每次都去调用__next__(),知道捕获到StopI异常,通过break结束循环,这样也能实现for循环的效果。我们来看一下结果。
跟for循环的结果相比,我们只是没有执行__iter__()方法,for循环会先来寻找__iter__()方法,来判断是否是可迭代的,只有可迭代的,for循环才会继续执行。
思考
针对上面的nba实例,如果我们连续使用两次for循环遍历,会有什么样的结果呢?
解析
我们来看一下下图的执行情况及结果。
从上图中的结果看出,执行第一遍for循环时,结果是正常的,执行第二时,直接就捕获了StopIteration异常,也就是没有找到nba中的元素。这是为什么呢?
原因就是,同一个迭代器对象不能多次迭代,迭代器只能向后移动,不能回到开始。
有什么办法可以解决呢?这就需要可迭代对象的帮忙啦!
三、可迭代对象
我们来具体看一下如何解决上面那个重复for循环的问题:
从上图中看到,我们在原有的ListIterator的基础上,又定义了一个新的类ListIterable,这个类除了初始化一个列表之外,只有__iter__()方法,并没有__next__()方法,并且__iter__()返回的是我们ListIterator的一个实例,也就是返回了一个迭代器对象。那么我们的ListIterable就是一个可迭代对象。
这样我们实例化一个可迭代对象,连续使用for循环,来看一下结果。
从上图中,我们看到这两次for循环都能遍历出nba中的元素,实现了多次迭代。
解析
其实这并不是多次迭代,只是在我们每次使用for循环时,都是找到的ListIterable中的__iter__()方法,而__iter__()方法会返回一个迭代器对象。所以这相当于我们每次都生成了一个新的迭代器对象ListIterator对象,这两个for循环对应的ListIterator并不是同一个,所以才能都遍历出来。
四、关于迭代器中的下一个元素
在迭代器中,我们要注意一个问题,就是迭代器只是记录了当前达到序列中的第几个元素,当我们的序列发生动态变化时,我们的结果也是实时更新的,来看下面这个例子。
从上图中看到,我们先定义了一个列表,通过for循环来遍历这个列表,输出当前的值之后,删除当前的值。我们来看一下结果。
为什么会这样呢?我们来逐次循环看一下:
第一次循环开始时,我们的列表为["火箭", "马刺", "独行侠", "灰熊", "鹈鹕"],此时我们索引为0,得到的值也就是"火箭",紧接着把"火箭"移除,列表发生动态变化,变成了["马刺", "独行侠", "灰熊", "鹈鹕"],此时的索引依旧为0,这个时候索引为0的值已经变成了"马刺",但此时第一次循环已经结束了,所以没有任何结果输出;
第二次循环开始时,列表为["马刺", "独行侠", "灰熊", "鹈鹕"],此时我们索引变成了1,在这个发生了变化的列表中,索引为1的值是"独行侠",所以我们得到的值就是"独行侠",紧接着把"独行侠"移除,列表发生动态变化,变成了["马刺", "灰熊", "鹈鹕"],索引为1的值变成了"灰熊",此时第二次循环也结束了;
第三次循环开始时,列表为["马刺", "灰熊", "鹈鹕"],此时索引变成了2,在这个新的列表中,索引为2的值是"鹈鹕",所以我们得到的值就是"鹈鹕",紧接着把"鹈鹕"移除,列表发生动态变化,变成了["马刺", "灰熊"],第三次循环结束;
第四次循环,索引变为3,超过了列表的长度,会捕获到StopIteration异常,循环结束。最后输出此时的列表,得到我们上图中的结果。
To Be Continued
第一次尝试写更复杂、更难懂的内容,希望通过文章中的实例大家可以理解迭代器,下次进阶篇我们将了解一个特殊的迭代器——生成器,大家敬请期待。
探测八个蛋∣跳出手工测试的井
领取专属 10元无门槛券
私享最新 技术干货