【Python】保姆级新手教程------第 11 章 迭代器 vs 生成器 第 11 章 迭代器 vs 生成器迭代器先区分两个概念可迭代对象iterable、迭代器iterator1️⃣可迭代对象iterable概念能被 for 循环遍历的对象就是可迭代对象iterable如下这些都是可迭代对象iterablenames[张三,李四,王五]citys(北京,上海,深圳)msghelloforiteminnames:print(item)如下这些不是可迭代对象iterableage10deftest():passforitemintest:print(item)endregion可迭代对象都拥有__iter__方法。names[张三,李四,王五]citys(北京,上海,深圳)msghelloage10deftest():passnames.__iter__()citys.__iter__()msg.__iter__()print(hasattr(names,__iter__))print(hasattr(citys,__iter__))print(hasattr(msg,__iter__))print(hasattr(age,__iter__))print(hasattr(test,__iter__))2️⃣迭代器iterator调用__iter__方法会得到迭代器(iterator)备注1__iter__是一个魔法方法当调用iter函数时__iter__会自动调用。备注2可迭代对象.__iter__()等价于iter(可迭代对象)。备注3如果iter(obj)能得到一个迭代器(iterator)那obj就是可迭代对象。names[张三,李四,王五]citys(北京,上海,深圳)msghelloprint(names.__iter__())print(citys.__iter__())print(msg.__iter__())print(iter(names))print(iter(citys))print(iter(msg))迭代器iterator拥有__next__方法每次调用都会根据当前的状态返回下一个元素。备注1迭代器.__next__()等价于next(迭代器)。备注2当所有元素全都取出后若继续调用__next__Python会抛出StopIteration异常。names[张三,李四,王五]ititer(names)print(it.__next__())print(it.__next__())print(it.__next__())print(it.__next__())print(next(it))print(next(it))print(next(it))print(next(it))编写for循环遍历names列表names[张三,李四,王五]foriteminnames:print(item)for循环背后的逻辑names[张三,李四,王五]# 1️⃣调用【可迭代对象的__iter__方法】获取到一个迭代器(iterator)ititer(names)# 2️⃣开启一个无限循环whileTrue:try:# 3️⃣调用__next__方法获取下一个元素itemnext(it)print(item)exceptStopIteration:# 4️⃣捕获 StopIteration 异常随后结束循环break迭代器iterator也拥有__iter__方法并且其返回值是迭代器自身。这么设计的原因让 for 循环也能遍历迭代器即为了让 iter(迭代器) 不出错。names[张三,李四,王五]ititer(names)print(it)resultiter(it)print(result)xiter(result)print(x)ititer(names)foriteminit:print(item):::info迭代器协议一个对象如果同时满足如下规范那该对象就是一个迭代器能被iter()接受。能被next()一步一步取值。:::迭代器是一次性的状态只会向前推进且不会自动重置迭代器在遍历的过程中会被“消耗”。names[张三,李四,王五]it1iter(names)it2iter(names)print(it1)print(it2)print(next(it1))print(next(it1))print(next(it1))print(next(it1))# 此行代码会抛出异常因为此时迭代器已经被耗尽了# 如想重新依次获取元素需要使用新的迭代器it2print(next(it2))print(next(it2))print(next(it2))3️⃣迭代器的应用需求让for循环可以遍历Person的实例对象。实现方式1️⃣classPerson:def__init__(self,name,age,gender,address):self.namename self.ageage self.gendergender self.addressaddressdef__iter__(self):returnPersonIterator(self)classPersonIterator:def__init__(self,p):# 将外部传进来的数据保存好self.pp# 设置迭代器的初始化状态指针位置self.index0# 配置好要遍历的内容self.attrs[p.name,p.age,p.gender,p.address]# 迭代器的__iter__方法会返回迭代器自身def__iter__(self):returnself# 每次调用__next__方法会根据当前的状态返回下一个元素def__next__(self):# 如果指针的位置超出范围那就抛出StopIteration异常ifself.indexlen(self.attrs):raiseStopIteration# 获取要返回的内容valueself.attrs[self.index]# 更新迭代器状态指针位置self.index1# 返回valuereturnvalue# 目标p1Person(张三,18,男,北京昌平)foriteminp1:print(item)foriteminp1:print(item)实现方式2️⃣classPerson:def__init__(self,name,age,gender,address):self.namename self.ageage self.gendergender self.addressaddress# 设置迭代器的初始化状态指针位置self.__index0# 配置好要遍历的内容self.__attrs[name,age,gender,address]def__iter__(self):self.__index0returnselfdef__next__(self):# 如果指针的位置超出范围那就抛出StopIteration异常ifself.__indexlen(self.__attrs):raiseStopIteration# 获取要返回的内容valueself.__attrs[self.__index]# 更新迭代器状态指针位置self.__index1# 返回valuereturnvalue# 目标# 下面的p1既是可迭代对象又是迭代器p1Person(张三,18,男,北京昌平)foriteminp1:print(item)foriteminp1:print(item)进阶迭代器玩的就是__next__fromcn2animportan2cnclassPerson:def__init__(self,name,age,gender,address):self.namename self.ageage self.gendergender self.addressaddress# 设置迭代器的初始化状态指针位置self.__index0# 配置好要遍历的内容self.__attrs[name,age,gender,address]def__iter__(self):self.__index0returnselfdef__next__(self):# 如果指针的位置超出范围那就抛出StopIteration异常ifself.__indexlen(self.__attrs):raiseStopIteration# 获取要返回的内容valueself.__attrs[self.__index]# 将字符串转为大写ifisinstance(value,str):valuevalue.upper()# 将数字转为汉语形式ifisinstance(value,int):valuean2cn(value)# 更新迭代器状态指针位置self.__index1# 返回valuereturnvalue# 目标# 下面的p1既是可迭代对象又是迭代器p1Person(zhangsan,18,男,北京昌平)foriteminp1:print(item)4️⃣迭代器的优势迭代器是惰性计算不会一次性生成所有结果所以能显著降低内存占用。当数据量很大不确定要用多少结果时推荐使用迭代器。使用迭代器实现【斐波那契数列】classFibo:def__init__(self,total):# 要生成多少个数self.totaltotal# 当前生成到第几个了计数器指针self.index0# 初始的两个值self.pre1self.cur1def__iter__(self):returnselfdef__next__(self):# 当生成足够数量后抛出StopIteration异常ifself.indexself.total:raiseStopIteration# 前两项都是1ifself.index2:value1else:# 新的结果等于前两项的和valueself.preself.cur# 更新一下pre和curself.preself.cur self.curvalue# 计数器1self.index1# 返回valuereturnvalue不使用迭代器实现【斐波那契数列】deffibo(total):iftotal0:return[]iftotal1:return[1]nums[1,1]foriinrange(2,total):nums.append(nums[-1]nums[-2])returnnums分析内存占用情况分别运行如下两段代码后会发现迭代器实现明显节约内存。tracemalloc.start()f1Fibo(0)mtracemalloc.get_traced_memory()[1]print(f内存占用是{m/1024/1024}MB)tracemalloc.start()f1fibo(0)mtracemalloc.get_traced_memory()[1]print(f内存占用是{m/1024/1024}MB)生成器1️⃣两个概念生成器函数函数体中如果出现了yield关键字那该函数是『生成器函数』。生成器对象调用『生成器函数』时其函数体不会立刻执行而是返回一个『生成器对象』。备注不管能否执行到yield所在的位置只要函数中有yield那该函数就是『生成器函数』。defdemo():print(demo函数开始执行了)print(100)yielda200print(a)ddemo()print(d)2️⃣几个细节写在『生成器函数』中的代码需要通过『生成器对象』来执行:::info调用『生成器对象』的__next__方法会让『生成器函数』中的代码开始执行。当『生成器函数』中的代码开始执行后遇到yield会“暂停”并会记录“暂停”的位置。后续调用__next__方法时都会从上一次“暂停”的位置继续运行直到再次遇到 yield。遇到return会抛出StopIteration异常并将return后面的表达式作为异常信息。yield后面所写的表达式会作为本次__next__方法的返回值。:::defdemo():print(demo函数开始执行了)print(100)yield我是第1个yield所返回的数据a200print(a)yield我是第2个yield所返回的数据b300print(b)return尚硅谷ddemo()r1next(d)print(r1)r2next(d)print(r2)try:next(d)exceptStopIterationase:print(e)生成器对象是一种特殊的迭代器本质是通过yield自动实现了迭代器协议defdemo():print(demo函数开始执行了)print(100)yield我是第1个yield所返回的数据a200print(a)yield我是第2个yield所返回的数据b300print(b)return尚硅谷ddemo()# 验证生成器对象d和迭代器一样也拥有__iter__ 和 __next__ 方法print(hasattr(d,__iter__))print(hasattr(d,__next__))# 验证生成器对象的__iter__方法和迭代器一样返回的也是自身resultiter(d)print(resultd)# for循环遍历生成器foritemind:print(item)# for循环背后的逻辑geniter(d)whileTrue:try:valuenext(gen)print(value)exceptStopIteration:breakyield也能写在循环里regiondefcreate_car(total):forindexinrange(1,total1):yieldf我是第{index}台车# cars是生成器对象carscreate_car(5)# 调用一次cars的__next__方法就会得到一台车c1next(cars)print(c1)c2next(cars)print(c2)c3next(cars)print(c3)c4next(cars)print(c4)c5next(cars)print(c5)forcarincars:print(car)endregionyield from能把一个『可迭代对象』里的东西依次yield出去。(替代for yield)defdemo():nums[10,20,30,40]yieldfromnums ddemo()r1next(d)print(r1)r2next(d)print(r2)r3next(d)print(r3)r4next(d)print(r4)foritemind:print(item)使用生成器.send(值)可以让生成器继续执行的同时给上一次yield传值。备注1next只能取值send既能取值也能送值。备注2第一次启动生成器不能传值或者说只能传 None 值defdemo():print(demo函数开始执行了)print(100)ayield我是第1个yield所返回的数据print(a)byield我是第2个yield所返回的数据print(b)return尚硅谷ddemo()r1next(d)# 此处等价于 d.send(None)print(r1)r2d.send(666)print(r2)try:d.send(888)exceptStopIterationase:print(e)3️⃣生成器的应用用生成器实现遍历Person类的实例对象classPerson:def__init__(self,name,age,gender,address):self.namename self.ageage self.gendergender self.addressaddress self.__attr[name,age,gender,address]def__iter__(self):# yield self.name# yield self.age# yield self.gender# yield self.addressyieldfromself.__attr p1Person(张三,18,男,北京昌平)# 目标forattrinp1:print(attr)用生成器实现斐波那契数列deffibo(total):pre1cur1forindexinrange(total):ifindex2:yield1else:valueprecur precur curvalueyieldvalue f1fibo(10)foriteminf1:print(item)无论是迭代器还是生成器对象都可以用list、tuple、set等直接拿到其里面的所有内容注意如果数据量很大可能会挤爆内存deffibo(total):pre1cur1forindexinrange(total):ifindex2:yield1else:valueprecur precur curvalueyieldvalue f1fibo(10)resultset(f1)print(result)4️⃣生成器表达式生成器表达式一种用类似列表推导式的语法快速创建生成器对象的方式。语法格式(表达式 for 变量 in 可迭代对象)。什么时候适合用生成器表达式———— 当“每个结果只依赖当前这一个元素”时。nums[10,20,30,40]# 列表推导式result1[n*2forninnums]print(result1)# 生成器表达式和列表推导式很像不要搞混result2(n*2forninnums)foriteminresult2:print(item)