范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Python中的for循环可迭代对象迭代器和生成器

  问题:之前在学习list和dict相关的知识时,遇到了一个常见的问题:如何在遍历list或dict的时候正常删除?例如我们在遍历dict的时候删除,会报错:RuntimeError: dictionary changed size during iteration;而在遍历list的时候删除,会有部分元素删除不完全。由这个问题又引发了我对另一个问题的思考:我们通过for循环去遍历一个list或dict时,具体是如何for的呢?即for循环的本质是什么?在查阅了相关资料后,我认识到这是一个和迭代器相关的问题,所以借此机会来详细认识一下Python中的for循环、可迭代对象、迭代器和生成器1. 迭代
  "迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。"在Python中,可迭代对象、迭代器、for循环都是和"迭代"密切相关的知识点。1.1 可迭代对象Iterable在Python中,称可以迭代的对象为可迭代对象。要判断一个类是否可迭代,只需要判断这个类是否为Iterable类的实例即可:>>> from collections.abc import Iterable >>> isinstance([], Iterable) True >>> isinstance(123, Iterable) False 复制代码上述提供了一个判断对象是否为可迭代对象的方法,那么一个对象怎么才是可迭代对象呢——只需要该对象的类实现了__iter__()方法即可:>>> class A: pass >>> isinstance(A(), Iterable) False >>> class B: def __iter__(self): pass >>> isinstance(B(), Iterable) True 复制代码由此可见,只要一个类实现了__iter__()方法,那么这个类的实例对象就是可迭代对象。注意这里的__iter__()方法可以没有任何内容。1.2 迭代器Iterator在Python中,通过Iterator类与迭代器相对应。相较于可迭代对象,迭代器只是多实现了一个__next__()方法:>>> from collections.abc import Iterator >>> class C: def __iter__(self): pass def __next__(self): pass >>> isinstance(C(), Iterator) True 复制代码显然,迭代器一定是可迭代对象(因为迭代器同时实现了__iter__()方法和__next__()方法),而可迭代对象不一定是迭代器。我们来看一下内建类型中的可迭代对象是否为迭代器:>>> isinstance(C(), Iterator) True >>> isinstance([], Iterable) True >>> isinstance([], Iterator) False >>> isinstance("123", Iterable) True >>> isinstance("123", Iterator) False >>> isinstance({}, Iterable) True >>> isinstance({}, Iterator) False 复制代码由此可见,str、list、dict对象都是可迭代对象,但它们都不是迭代器。至此,我们对可迭代对象和迭代器有了一个基本概念上的认识,也知道了有__iter__()和__next__()这两种方法。但是这两个魔法方法究竟是如何使用的呢?它们和for循环又有什么关系呢?1.3 for循环1.3.1 iter()方法和next()方法iter()方法和next()方法都是Python提供的内置方法。对对象使用iter()方法会调用对象的__iter__()方法,对对象使用next()方法会调用对象的__next__()方法。下面我们具体看一下它们之间的关系。1.3.2 iter()和__iter__()__iter__()方法的作用就是返回一个迭代器,一般我们可以通过内置函数iter()来调用对象的__iter__()方法1.2中举的例子,只是简单的实现了__iter__()方法,但函数体直接被pass掉了,本质上是没有实现迭代功能的,现在我们来看一下__iter__()正常使用时的例子:>>> class A: def __iter__(self): print("执行A类的__iter__()方法") return B() >>> class B: def __iter__(self): print("执行B类的__iter__()方法") return self def __next__(self): pass >>> a = A() >>> a1 = iter(a) 执行A类的__iter__()方法 >>> b = B() >>> b1 = iter(b) 执行B类的__iter__()方法 复制代码可以看到,对于类A,我们为它的__iter__()方法设置了返回值为B(),而B()就是一个迭代器;而对于类B,我们在它的__iter__()方法中直接返回了它的实例self,因为它的实例本身就是可迭代对象。当然这里我们也可以返回其他的迭代器,但是如果__iter__()方法返回的是一个非迭代器,那么当我们调用iter()方法时就会报错:>>> class C: def __iter__(self): pass >>> iter(C()) Traceback (most recent call last): File "", line 1, in  iter(C()) TypeError: iter() returned non-iterator of type "NoneType" >>> class D: def __iter__(self): return [] >>> iter(D()) Traceback (most recent call last): File "", line 1, in  iter(D()) TypeError: iter() returned non-iterator of type "list" 复制代码1.3.3 next()和__next__()__next__()方法的作用是返回遍历过程中的下一个元素,如果没有下一个元素,则会抛出StopIteration异常,一般我们可以通过内置函数next()来调用对象的__next__()方法下面我们以list对象为例,来看一下next是如何遍历的:>>> l1 = [1, 2, 3] >>> next(l1) Traceback (most recent call last): File "", line 1, in  next(l1) TypeError: "list" object is not an iterator 复制代码可以看到,当我们直接对列表对象l1使用next()方法时,会报错"list" object is not an iterator,显然list对象并不是迭代器,也就是说它没有实现__next__()方法,那么我们怎么才能去"对一个列表对象使用next()"呢——根据我们前面介绍的__iter__()方法,我们知道它会返回一个迭代器,而迭代器是实现了__next__()方法的,所以我们可以先对list对象使用iter__(),获取到它对应的迭代器,然后对这个迭代器使用next()就可以了:>>> l1 = [1, 2, 3] >>> l1_iter = iter(l1) >>> type(l1_iter)  >>> next(l1_iter) 1 >>> next(l1_iter) 2 >>> next(l1_iter) 3 >>> next(l1_iter) Traceback (most recent call last): File "", line 1, in next(l1_iter) StopIteration 复制代码思考:__next__()为什么要不停地去取出元素,并且在最后去抛出异常,而不是通过对象的长度相关信息来确定调用次数?个人认为是因为我们可以通过next()去手动调用对象的__next__()方法,而在next()中并没有判断对象的长度,所以需要在__next__()去处理1.3.4 自定义类实现__iter__()和__next__()
  下面我们试着通过实现自定义一下list的迭代过程:首先我们定义一个类A,它是一个可迭代对象,__iter__()方法会返回一个迭代器B(),并且还拥有一个成员变量m_Lst:>>> class A: def __init__(self, lst): self.m_Lst = lst def __iter__(self): return B(self.m_Lst) 复制代码对于迭代器的类B,我们实现它的__iter__()方法和__next__()方法,注意在__next__()方法中我们需要抛出StopIteration异常。此外,它拥有两个成员变量self.m_Lst和self.m_Index用于迭代遍历:>>> class B: def __init__(self, lst): self.m_Lst = lst self.m_Index= 0 def __iter__(self): return self def __next__(self): try: value = self.m_Lst[self.m_Index] self.m_Index += 1 return value except IndexError: raise StopIteration() 复制代码至此,我们已经完成了迭代器的准备工作,下面我们来实践一下迭代吧,为了更好地展示这个过程,我们可以加上一些打印:>>> class A: def __init__(self, lst): self.m_Lst = lst def __iter__(self): print("call A().__iter__()") return B(self.m_Lst) >>> class B: def __init__(self, lst): self.m_Lst = lst self.m_Index= 0 def __iter__(self): print("call B().__iter__()") return self def __next__(self): print("call B().__next__()") try: value = self.m_Lst[self.m_Index] self.m_Index += 1 return value except IndexError: print("call B().__next__() except IndexError") raise StopIteration() >>> l = [1, 2, 3] >>> a = A(l) >>> a_iter = iter(a) call A().__iter__() >>> next(a_iter) call B().__next__() 1 >>> next(a_iter) call B().__next__() 2 >>> next(a_iter) call B().__next__() 3 >>> next(a_iter) call B().__next__() call B().__next__() except IndexError Traceback (most recent call last): File "", line 11, in __next__ value = self.m_Lst[self.m_Index] IndexError: list index out of range During handling of the above exception, another exception occurred: Traceback (most recent call last): File "", line 1, in  next(a_iter) File "", line 16, in __next__ raise StopIteration() StopIteration 复制代码可以看到,我们借助iter()和next()方法能够很好地将整个遍历的过程展示出来。至此,我们对可迭代对象、迭代器以及__iter__()和__next__()都有了一定的认识,那么,for循环和它们有什么关系呢?1.3.5 探究for循环for循环是我们使用频率最高的操作之一,我们一般会用它来遍历一个容器(列表、字典等),这些容器都有一个共同的特点——都是可迭代对象。那么对于我们自定义的类A,它的实例对象a应该也可以通过for循环来遍历:>>> for i in a: print(i) call A().__iter__() call B().__next__() 1 call B().__next__() 2 call B().__next__() 3 call B().__next__() call B().__next__() except IndexError >>> for i in a: pass call A().__iter__() call B().__next__() call B().__next__() call B().__next__() call B().__next__() call B().__next__() except IndexError 复制代码通过打印,我们可以清楚的看到:对一个可迭代对象使用for循环进行遍历时,for循环会调用该对象的__iter__()方法来获取到迭代器,然后循环调用该迭代器的__next__()方法,依次获取下一个元素,并且最后会捕获StopIteration异常(这里可以尝试在类B的__next__()方法最后只捕获IndexError而不抛出StopIteration,则for循环此时会无限循环)既然我们提到了for循环会自动去捕获StopIteration异常,当没有捕获到StopIteration异常时会无限循环,那么我们是否可以用while循环来模拟一下这个过程呢?>>> while True: try: i = next(a_iter) print(i) except StopIteration: print("except StopIteration") break call B().__next__() 1 call B().__next__() 2 call B().__next__() 3 call B().__next__() call B().__next__() except IndexError except StopIteration 复制代码到这里,大家应该对for对可迭代对象遍历的过程有了一定的了解,想要更深入了解的话可以结合源码进一步学习(本次学习分享主要是结合实际代码对一些概念进行讲解,并未涉及到相应源码)。2 生成器
  迭代器和生成器总是会被同时提起,那么它们之间有什么关联呢——生成器是一种特殊的迭代器。2.1 获取生成器当一个函数体内使用yield关键字时,我们就称这个函数为生成器函数;当我们调用这个生成器函数时,Python会自动在返回的对象中添加__iter__()方法和__next__()方法,它返回的对象就是一个生成器。代码示例:>>> from collections.abc import Iterator >>> def generator(): print("first") yield 1 print("second") yield 2 print("third") yield 3 >>> gen = generator() >>> isinstance(gen, Iterator) True 复制代码2.2 next(生成器)既然生成器是一种特殊的迭代器,那么我们对它使用一下next()方法:>>> next(gen) first 1 >>> next(gen) second 2 >>> next(gen) third 3 >>> next(gen) Traceback (most recent call last): File "", line 1, in  next(gen) StopIteration 复制代码这里我想给这个generator()函数加一个return,最后会在抛出异常时打印这个返回值(这里我对Python异常相关的知识了解比较少,不太清楚这个问题,以后再补充吧):>>> from collections.abc import Iterator >>> def generator(): print("first") yield 1 print("second") yield 2 print("third") yield 3 return "end" >>> gen = generator() >>> isinstance(gen, Iterator) True >>> next(gen) first 1 >>> next(gen) second 2 >>> next(gen) third 3 >>> next(gen) Traceback (most recent call last): File "", line 1, in  next(gen) StopIteration: end 复制代码可以看到,当我们对生成器使用next()方法时,生成器会执行到下一个yield为止,并且返回yield后面的值;当我们再次调用next(生成器)时,会继续向下执行,直到下一个yield语句;执行到最后再没有yield语句时,就会抛出StopIteration异常2.3 生成器和迭代器通过上面的过程,我们知道了生成器本质上就是一种迭代器,但是除了yield的特殊外,生成器还有什么特殊点呢——惰性计算。这里的惰性计算是指:当我们调用next(生成器)时,每次调用只会产生一个值,这样的好处就是,当遍历的元素量很大时,我们不需要将所有的元素一次获取,而是每次只取其中的一个元素,可以节省大量内存。(个人理解:这里注意和上面的迭代器的next()区别开,对于迭代器,虽然每次next()时,也只会返回一个值,但是本质上我们已经把所有的值存储在内存中了(比如类A和类B的self.m_Lst),但是对于生成器,内存中并不会将所有的值先存储起来,而是每次调用next()就获取一个值)下面我们来看一个实际的例子:输出10000000以内的所有偶数(注意,如果实际业务环境下需要存储,那就根据实际情况来,这里只是针对两者的区别进行讨论)首先我们通过迭代器来实现:(这里直接使用列表)>>> def iterator(): lst = [] index = 0 while index <= 10000000: if index % 2 == 0: print(index) lst.append(index) index += 1 return lst >>> result = iterator() 复制代码然后通过生成器来实现:>>> def generator(): index = 0 while index <= 10000000: if index % 2 == 0: yield index index += 1 >>> gen = generator() >>> next(gen) 0 >>> next(gen) 2 >>> next(gen) 4 >>> next(gen) 6 >>> next(gen) 8 复制代码由于采取了惰性运算,生成器也有它的不足:对于列表对象、字典对象等可迭代对象,我们可以通过len()方法直接获取其长度,但是对于生成器对象,我们只知道当前元素,自然就不能获取到它的长度信息了。下面我们总结一下生成器和迭代器的相同点和不同点:生成器是一种特殊的迭代器;迭代器会通过return来返回值,而生成器则是通过yield来返回值,对生成器使用next()方法,会在每一个yield语句处停下;迭代器会存储所有的元素,但是生成器采用的是惰性计算,只知道当前元素。2.4 生成器解析式列表解析式是我们常用的一种解析式:(类似的还有字典解析式、集合解析式)>>> lst = [i for i in range(10) if i % 2 == 1] >>> lst [1, 3, 5, 7, 9] 复制代码而生成器解析式和列表解析式类似,我们只需要将[]更换为()即可:(把元组解析式给抢了,hh)>>> gen = (i for i in range(10) if i % 2 == 1) >>> gen  at 0x00000193E2945A80> >>> next(gen) 1 >>> next(gen) 3 >>> next(gen) 5 >>> next(gen) 7 >>> next(gen) 9 >>> next(gen) Traceback (most recent call last): File "", line 1, in  next(gen) StopIteration 复制代码至此,我们就有了生成器的两种创造方式:生成器函数(yield)返回一个生成器生成器解析式返回一个生成器3 解决问题最后回到我们最初的问题:如何在遍历list或dict的时候正常删除?首先我们来探寻一下出错的原因,以list对象为例:>>> lst = [1, 2, 3] >>> for i in lst: print(i) lst.remove(i) 1 3 复制代码可以看到,我们在遍历打印列表元素的同时删除当前元素,实际的输出和我们需要的输出并不一样。以下是个人理解(想更准确地解答这个问题可能需要进一步结合源码):remove删除列表元素时,列表元素的索引会发生变化(这是因为Python底层列表是通过数组实现的,remove方法删除元素时需要挪动其他元素,具体分析我后续会补充相关源码学习笔记,这里先了解即可)类比我们自定义实现的迭代器,可以看到我们会在__next__()方法中对索引进行递增:>>> class A: def __init__(self, lst): self.m_Lst = lst def __iter__(self): print("call A().__iter__()") return B(self.m_Lst) >>> class B: def __init__(self, lst): self.m_Lst = lst self.m_Index= 0 def __iter__(self): print("call B().__iter__()") return self def __next__(self): print("call B().__next__()") try: value = self.m_Lst[self.m_Index] self.m_Index += 1 return value except IndexError: print("call B().__next__() except IndexError") raise StopIteration() 复制代码那么我们可以猜测:列表对象对应的迭代器,应该也是会有一个索引成员变量,用于在__next__()方法中进行定位(这里没看过源码,只是个人猜想)当我们使用for循环遍历列表对象时,实际上是通过next()方法对其对应的迭代器进行操作,此时由于remove()方法的调用,导致列表元素的索引发生了改变(原来元素3的索引是2,删除元素2之后索引变为了1),所以在__next__()方法中,此时需要遍历的元素索引为1,而元素3顶替了这个位置,所以最后的输出为1,3。dict和list类似,不过在遍历时删除dict中的元素时会直接报错,具体原因大家也可以自行分析。

LVE型零泄漏真空热管换热装置通过专家鉴定日前,由龙净电除尘与脱硝事业部牵头与国家电投江西电力公司联合研发,并成功应用于景德镇电厂660MW低低温电除尘系统中的LVE型零泄漏真空热管换热装置通过专家鉴定。此次鉴定会由中国环中国自动化集团流程工业自动化产业项目落户滨江2021年9月7日,中国自动化集团与杭州高新区(滨江)战略合作签约仪式在北京圆满举行。中国自动化集团董事局主席宣瑞国董事周政强及杭州高新区(滨江)党委副书记管委会主任区长李志龙,管120万吨乙烯装置裂解气压缩机机组驱动用汽轮机单机试车成功9月28日晚上,镇海炼化试车间传来一阵欢呼声。由杭汽轮股份公司自主研发设计并制造,应用于镇海炼化年产120万吨乙烯装置裂解气压缩机机组驱动用汽轮机单机试车取得一次性圆满成功。镇海炼新时达苏州办事处正式揭牌启用10月8日下午,新时达苏州办事处开业典礼在苏州南师大科技园隆重举行。此次苏州办事处的成立,预示着新时达在补强及拉通销售网络增强本地客户服务能力方面,将开启新的进程。新时达董事总经理兰石在自动化集成钻机研制领域取得新突破近日,兰石装备公司钻机总装试验场起升了一台ZJ90DB自动化集成钻机,其机具设备及控制系统均采用创新设计,标志着装备公司向产品自动化智能化转型的路上迈出了坚实一步。不同于以往同级别东方风电率先完成10MW级海上风电机组碳足迹分析近日,东方风电携手DNV(挪威船级社)率先完成国内首个10MW级海上机组碳足迹评估分析工作,为机组全价值链持续减碳,以提供更加清洁低碳的绿色电力,降低生命周期内每度电的碳足迹提供了国内首个新型二氧化碳储能验证项目在东方汽轮机开工建造2021年10月22日,在东方电气集团东方汽轮机有限公司八角园区,由百穰新能源科技(深圳)有限公司东方汽轮机有限公司和西安交通大学能源与动力工程学院共同举行国内首个新型二氧化碳储能领创智能产线正式交付湖南星邦智能工厂科技创新是引领发展的第一生产力,智能工厂是工厂的未来。在全球智能化的环境下,作为国内智能激光装备主导品牌,领创激光一直在探索新型工业模式的路上。前段时间,领创中标了湖南星邦智能工厂广州进行第四次核酸检测近日广州市政府发文要在30日前完成第四轮全员核酸检测,全部解封就靠这次结果了。中午吃饭经过路边时,发现街道工作人员拿着喇叭在通知做核酸的地点人少可以现在过去做。根据指引步行一百米左常见主板检测卡检测错误排障办法以及检测顺序开机检测顺序00或FF(基础自检(包括CPU供电))C0(CPU)C1(主内存)C4C5C6(扩展内存)2A(显卡)38(显示硬件配置基础信息)75(检查外部储存设备)85(进入B世界首个非补燃压缩空气储能电站并网试验成功2021年9月30日,由东方汽轮机自主研发制造的国内首台压缩空气储能国家示范项目透平机组,在江苏省常州市金坛区盐穴压缩空气国家试验项目现场顺利实现冲转并网,向国家电网发出我国首个大
为什么小米和红米明明是一家人,却要被分割成两个品牌?一般来说,大众消费者口中的小米手机,指的是小米主品牌红米子品牌的手机。从这里我们可以发现,普通用户普遍把小米和红米混为一谈,但小米官方更倾向于进行内部分流,将主品牌和子品牌分割开来广汽埃安古惠南员工将持股,埃安没有科长及以下的干部以前公司的组织体系是按总经理副总经理,下面是部长科长系长。现在没有科长及以下的干部了,都变成项目管理制了,没有行政职务。有经理,但是不会有科长。11月19日,广汽埃安新能源汽车有限天玑1100仅1399,红米Note10Pro再降低价,是否值得入手?红米Note系列是千元机的标杆,每一代的销量都能达到几千万台。但今年可能会差一些,并不是因为红米Note系列不好了,而是今年发布了两代Note系列,自身也形成了一定的分流。今年上半iPhone13黑解机解惑,到底值得买吗?今年iPhone13系列加量不加价,虽然降价了当时仍然是贵啊,基础版的iPhone13起售价要5999元,这钱安卓顶级旗舰不是随便买。所以预算有限的朋友把目光投向了黑解机。那么黑解华为造车在赛力斯丢的脸,能靠与长安合作的阿维塔赚回来吗?知嘹汽车木易旋凌自主品牌发展高端新能源车型,最好的办法就是重新建立一个品牌,然后通过营销和换赛道的市场,来打造出更高端的品牌形象,这也是目前自主品牌惯用的做法。当然,像雷克萨斯,英不想升级到win11?教你安装Windows10LTSC2021版,暂别win11Windows11发布至今,bug出了一堆也修复了一堆,但是好像很多用户升级到win11的意愿还是不大,甚至有的升级了还是想退win10。但是微软的操作是有点狗的,即使你重装回wi论为什么不要买整机整机有三种第一种小作坊整机,这类整机可以说是便宜的离谱,低则几百块钱多则上千,他们都是用的什么配件呢?打开你的淘宝,看见这么便宜的整机wow,就问你心动了没有?然而这电脑年龄可能与写一写和手机有关的回忆从初中到大学我一共买过6部手机。现在正在用第六部手机写文章。第一部手机是一部老人机,因为当时的我还只是初中生而已。所以只能用老人机。老人机是如何来的我已经忘了,现在已经不再使用了。如何才能提高效率?提高效率,通常指的是学习效率,工作效率和生活效率吧。如何提高学习效率?一,学科管理假如你是个学生,就得分清主课,副课之别,投入的时间和精力也应该有所轻重不同,取得的成绩,显然也会不曝京东方今年向苹果提供超千万屏幕,iPhone12让路新机价格滑铁卢作为国内显示器的领先厂商,京东方近年来也与苹果关系暧昧,而苹果在供应链上一直要求很高,这也彰显了京东方的实力。而今年,有很多消息称京东方的OLED生产质量进一步提升,进入最新的iPiPhone14好消息,接口终于要变了一般来说苹果会在9月份的时候推出新款iPhone手机,比如今年9月推出的iPhone13系列手机,现在距离iPhone13系列手机推出已经有一段时间关于iPhone14系列手机的爆