百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

干货 | 一文讲清楚Python之迭代器

yuyutoo 2025-03-24 22:21 6 浏览 0 评论

概念引入

在之前的教程中,我们已经接触过一些典型的for语句,比如:

>>> list_example = [0, 1, 2, 3, 4]
>>> for i in list_example:
...  print(i)
...
0
1
2
3
4

通过简单地使用forin两个关键字,我们可以很轻松地实现在 C 语言中繁琐的遍历操作。相比较而言,C 语言中要实现相同的功能,需要这样写(假设存在整型数组list_example):

int i;
for(i = 0; i < list_length; i++)
    printf("%d\n", list_example[i]);

显而易见,在遍历元素的操作上,Python 的表达更加直观优雅,简洁明了;这正是因为 Python 在实现for语句的时候,恰到好处地使用了“迭代器”的概念。

迭代器在 Python 中随处可见,并且具有统一的标准。通过使用迭代器,Python 能够逐个访问列表list_example中的每个元素。

定义及原理

迭代器的定义

迭代器(iterator)是一种可在容器(container)中遍访的接口,为使用者封装了内部逻辑。

而具体到 Python 中,迭代器也属于内置的标准类之一,是与我们之前学习过的“序列”同一层次的概念。

对于迭代器对象本身来说,需要具有__iter__()__next__()两种方法,二者合称为“迭代器协议”。也就是说,只要同时具有这两种方法,Python 解释器就会认为该对象是一个迭代器;反之,只具有其中一个方法或者二者都不具有,解释器则认为该对象不是一个迭代器。

上述论断可由下面的代码验证(需要用到内置函数isinstance(),来判断一个对象是否是某个类的实例):

>>> from collections import Iterable, Iterator, Container
>>> class bothIterAndNext:
... 	def __iter__(self):
... 		pass
... 	def __next__(self):
... 		pass
...
>>> isinstance(bothIterAndNext(), Iterable) # 两种方法都有的对象是可迭代的
True
>>> isinstance(bothIterAndNext(), Iterator) # 两种方法都有的对象是迭代器
True
>>> 
>>> class onlyNext:
... 	def __next__(self):
... 		pass
...
>>> isinstance(onlyNext(), Iterable) # 只有方法 __next__() 是不可迭代的
False
>>> isinstance(onlyNext(), Iterator) # 只有方法 __next__() 不是迭代器
False
>>> 
>>> class onlyIter:
... 	def __iter__(self):
... 		pass
...
>>> isinstance(onlyIter(), Iterable) # 只有方法 __iter__() 是可迭代的
True
>>> isinstance(onlyIter(), Iterator) # 只有方法 __iter__() 不是迭代器
False

由第 8~11 行的代码可知,对于 Python 来说,判断一个对象是否是迭代器的标准仅仅是“是否同时具有__iter__()__next__()这两个方法”。

并且从第 17~20 行的代码也可以验证上述推断:只具有方法__next__()既不是可迭代的,也不是一个迭代器。

有意思的事情发生在代码第 26、27 两行:代码输出结果显示,只有方法__iter__()的对象居然是可迭代的!

迭代器的实质

迭代器对象本质上代表的是一个数据流,通过反复调用其方法__next__()或将其作为参数传入next()函数,即可按顺序逐个返回数据流中的每一项;直到流中不再有数据项,从而抛出一个StopIteration异常,终止迭代。

在 Python 中内置了两个函数:iter()next(),分别用于“将参数对象转换为迭代器对象”和“从迭代器中取出下一项”。

实际上所有具有方法__iter__()的对象均被视作“可迭代的”。因为方法__iter__()进行的操作其实就是返回一个该对象对应的迭代器,也就是说“可迭代的(iterable)”的真实含义其实是“可以被转换为迭代器(iterator)的”。而内置函数iter()也是调用对象本身具有的__iter__()方法来实现特定对象到迭代器的转换。

相应地,内置函数next()其实是调用了对象本身的方法__next__(),而该方法执行的操作就是从对象对应的数据流中取出下一项。

因此直接调用对象的__iter__()__next__()方法与将对象作为参数传入内置函数iter()next()是等效的。

要注意的一点在于,对迭代器调用其本身的__iter__()方法,得到的将会是这个迭代器自身,该迭代器相关的状态都会被保留,包括该迭代器目前的迭代状态。见下述代码:

>>> li = [1, 2, 3]
>>> li_iterator = iter(li)
>>> isinstance(li, Iterator)
False
>>> isinstance(li_iterator, Iterator)
True

显然,列表li本身并不是一个迭代器,而将其传入内置函数iter()就得到了相应于列表li的迭代器li_iterator。我们调用next()函数来迭代它:

>>> next(li_iterator)
1
>>> next(li_iterator)
2

一切都在预料之中。我们再来将其本身作为参数传入内置函数iter()

>>> li_iterator = iter(li_iterator)
>>> next(li_iterator)
3

到这里跟我们希望的就有所出入了。在使用这样一个语句的时候,通常我们的目的都是得到一个新的迭代器,而非跟原先的迭代器一样的对象。

更进一步地,我们还可以发现,对迭代器调用iter()函数得到的对象不仅与原先的迭代器具有相同的状态,它们其实就是指向同一个对象

>>> id(li_iterator)
2195581916440
>>> li_iterator = iter(li_iterator)
>>> id(li_iterator)
2195581916440
>>> li_iterator2 = iter(li_iterator)
>>> id(li_iterator2)
2195581916440

也就是说在对象本身就是一个迭代器的情况下,生成的对应迭代器的时候 Python 不会进行另外的操作,就返回这个迭代器本身作为结果。

实现一个迭代器类

有了上面的讨论,我们就可以自己实现一个简单的迭代器。只要确保这个简单迭代器具有与迭代器定义相符的行为即可。

说人话就是:要定义一个数据类型,具有__iter__()方法并且该方法返回一个带有__next__()方法的对象,而当该类已经具有__next__()方法时则返回其本身。示例代码如下:

class Reverse:
    """反向遍历序列对象的迭代器"""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

验证一下:

>>> rev = Reverse('justdopython.com')
>>> next(rev)
'm'
>>> next(rev)
'o'
>>> next(rev)
'c'
>>> next(rev)
'.'

for语句与迭代器

回到文章开头我们作为引子的for循环示例,实际上在执行for语句的时候,Python 悄悄调用了内置函数iter(),并将for语句中的容器对象作为参数传入;而函数iter()返回值则是一个迭代器对象。

因此,for语句是将容器对象转换为迭代器对象之后,调用__next__()方法,逐个访问原容器中的各个对象,直到遍历完所有元素,抛出一个StopIteration异常,并终止for循环。

总结

  • 迭代器(iterator)首先要是可迭代的(iterable);即迭代器一定是可迭代的,但可迭代的不一定是迭代器
  • 可迭代的对象意味着可以被转换为迭代器
  • 迭代器需要同时具有方法__iter__()__next__()
  • 对迭代器调用iter()函数,得到的是这个迭代器本身
  • for循环实际上使用了迭代器,并且一般情况下将异常StopIteration作为循环终止条件

本文探究了 Python 中迭代器的相关知识点,深入理解了迭代器的属性和行为,学到了两个重要的方法__iter__()__next__()。同时搞明白了 Python 实现for循环的内部机制。

相关推荐

《保卫萝卜2》安卓版大更新 壕礼助阵世界杯

《保卫萝卜2:极地冒险》本周不仅迎来了安卓版本的重大更新,同时将于7月4日本周五,带来“保卫萝卜2”安卓版本世界杯主题活动的火热开启,游戏更新与活动两不误。一定有玩家会问,激萌塔防到底进行了哪些更新?...

儿童手工折纸:胡萝卜,和孩子一起边玩边学carrot

1、准备两张正方形纸,一橙一绿,对折出折痕。2、橙色沿其中一条对角线如图折两三角形。3、把上面三角折平,如图。4、绿色纸折成三角形。5、再折成更小的三角形。6、再折三分之一如图。7、打开折纸,压平中间...

《饥荒》食物代码有哪些(饥荒最新版代码总汇食物篇)

饥荒游戏中,玩家们需要获取各种素材与食物,进行生存。玩家们在游戏中,进入游戏后按“~”键调出控制台使用代码,可以直接获得素材。比如胡萝卜的代码是carrot,玉米的代码是corn,南瓜的代码是pump...

Skyscanner:帮你找到最便宜机票 订票不求人

你喜欢旅行吗?在合适的时间、合适的目的地,来一场说走就走的旅行?机票就是关键!Skyscanner这款免费的手机应用,在几秒钟内比较全球600多家航空公司的航班安排、价格和时刻表,帮你节省金钱和时间。...

小猪佩奇第二季50(小猪佩奇第二季英文版免费观看)

Sleepover过夜Itisnighttime.现在是晚上。...

我在民政局工作的那些事儿(二)(我在民政局上班)

时间到了1997年的秋天,经过一年多的学习和实践,我在处理结婚和离婚的事情更加的娴熟,也获得了领导的器重,所以我在处理平时的工作时也能得心应手。这一天我正在离婚处和同事闲聊,因为离婚处几天也遇不到人,...

夏天来了就你还没瘦?教你不节食13天瘦10斤的哥本哈根减肥法……

好看的人都关注江苏气象啦夏天很快就要来了你是否和苏苏一样身上的肉肉还没做好准备?真是一个悲伤的故事……下面这个哥本哈根减肥法苏苏的同事亲测有效不节食不运动不反弹大家快来一起试试看吧~DAY1...

Pursuing global modernization for peaceful development, mutually beneficial cooperation, prosperity for all

AlocalworkeroperatesequipmentintheChina-EgyptTEDASuezEconomicandTradeCooperationZonei...

Centuries-old tea road regains glory as Belt and Road cooperation deepens

FUZHOU/ST.PETERSBURG,Oct.2(Xinhua)--NestledinthepicturesqueWuyiMountainsinsoutheastChi...

15 THE NUTCRACKERS OF NUTCRACKER LODGE (CONTINUED)胡桃夹子小屋里的胡桃夹子(续篇)

...

AI模型部署:Triton Inference Server模型部署框架简介和快速实践

关键词:...

Ftrace function graph简介(flat function)

引言由于android开发的需要与systrace的普及,现在大家在进行性能与功耗分析时候,经常会用到systrace跟pefetto.而systrace就是基于内核的eventtracing来实...

JAVA历史版本(java各版本)

JAVA发展1.1996年1月23日JDK1.0Java虚拟机SunClassicVM,Applet,AWT2.1997年2月19日JDK1.1JAR文件格式,JDBC,JavaBea...

java 进化史1(java的进阶之路)

java从1996年1月第一个版本诞生,到2022年3月最新的java18,已经经历了27年,整整18个大的版本。很久之前有人就说java要被淘汰,但是java活到现在依然坚挺,不知道java还能活...

学习java第二天(java学完后能做什么)

#java知识#...

取消回复欢迎 发表评论: