ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

迭代器与生成器

迭代器与生成器 可迭代对象能被 for 循环遍历的对象被称为可迭代对象iterable可迭代对象iterable能调用 __iter__ 方法迭代器调用 __iter__ 方法会得到迭代器iterator备注1__iter__ 方法是一个魔法方法当调用 iter 函数时__iter__ 会自动调用备注2可迭代器对象.__iter__() 等价于 iter(可迭代器对象)备注3如果 iter(obj) 能得到一个迭代器iterator那obj就是可迭代对象。如何判断一个对象是可迭代对象name [张三, 李四, 王五] #list_iterator object at 0x000001DA82AF05B0 print(name.__iter__()) #list_iterator object at 0x000001CFB81905B0 print(iter(name))迭代器iterator拥有 __next__ 方法每次调用都会根据当前的状态返回下一个元素备注1迭代器.__next__() 等价于 next(迭代器)备注2当所有元素全都取出后若继续调用 __next__ 方法Python会抛出 StopIteration 异常。#可迭代对象 name [张三, 李四, 王五] #迭代器 it iter(name) print(next(it))#张三 print(next(it))#李四 print(next(it))#王五 print(next(it))#抛出 StopIteration 异常 # print(it.__next__())#张三 # print(it.__next__())#李四 # print(it.__next__())#王五 # print(it.__next__())#抛出 StopIteration 异常for 循环背后的工作逻辑#可迭代对象 name [张三, 李四, 王五] #编写for循环遍历names列表 for item in name: print(item)#for循环背后的逻辑 #调用可迭代器对象的__iter__方法获得一个迭代器 it iter(name) # 开启一个无限循环 while True: try: #调用迭代器的__next__方法获取下一个元素 item next(it) print(item) except StopIteration: #捕获StopIteration异常结束循环 break迭代器iterator也拥有 __iter__ 方法并且其返回值是迭代器本身这么设计的原因如下让 for 循环也能遍历迭代器即为了让 iter(迭代器)不出错#可迭代对象 name [张三, 李四, 王五] it iter(name) print(it)#list_iterator object at 0x0000019DC7A205B0 res1 iter(it) print(res1)#list_iterator object at 0x0000019DC7A205B0 res2 iter(res1) print(res2)#list_iterator object at 0x000001F895D805B0 #正常遍历 for item in it: print(item)迭代器协议能被 iter() 接受能被 next() 一步一步取值符合上面两个条件就符合 迭代器协议就是迭代器。手写迭代器特性迭代器是一次性的状态只会向前推进且不会自动重置迭代器在遍历过程中会被“消耗”#可迭代对象 name [张三, 李四, 王五] it1 iter(name) # 状态只会向前推进 print(next(it1)) print(next(it1)) print(next(it1)) # 由于迭代器不会自动重置需要重新获取迭代器 it2 iter(name) print(next(it2)) print(next(it2)) print(next(it2))注意点name [张三, 李四, 王五] it3 iter(name) for i in it3: print(i) #由于不会自动重置这里不会遍历直接抛异常退出循环了 for i in it3: print(i)实现迭代器需求让 for 循环可以遍历 Person 类的实例对象实现方式一实现两个类一个是可迭代器对象的类型另一个迭代器类。注意点迭代器中需要保存好外部传进来的数据迭代器中需要维护一个状态指针位置迭代器需要配置好遍历内容迭代器的实现需要符合迭代器协议即实现__iter__和__next__方法class Person: def __init__(self,name, age, gender, address): self.name name self.age age self.gender gender self.address address def __iter__(self): #返回一个迭代器 return PersonIterator(self) #实现Person迭代器类 class PersonIterator: def __init__(self,p): #将外部传进来的数据保存好 self.p p #设置迭代器初始化状态指针位置 self.index 0 #配置好要遍历的内容 self.addr [p.name, p.age, p.gender, p.address] # 迭代器的__iter__方法返回迭代器自身 def __iter__(self): return self #每次调用__next__方法会根据当前状态返回下一个元素 def __next__(self): #如果迭代器当前状态超出范围那就抛出StopIteration异常 if self.index len(self.addr): raise StopIteration #获取返回内容 val self.addr[self.index] #更新迭代器状态指针位置 self.index 1 return val p1 Person(张三, 18, 男, 上海) for item in p1: print(item)实现方式二使用一个类。该类即是 可迭代器对象又是 迭代器。注意点实现了迭代器协议就是 迭代器可以调用iter方法返回迭代器就是可迭代器对象。class Person: def __init__(self,name, age, gender, address): self.name name self.age age self.gender gender self.address address #需要设置为私有属性不然实例对象可以访问 # 设置迭代器初始化状态指针位置 self.__index 0 # 配置好要迭代器的内容 self.__addr [name,age,gender,address] #迭代器的__iter__方法返回迭代器自身 def __iter__(self): #这里是为了可以在外部多次变量Person的实例对象 #但是会一个bug外部也可以多次遍历Person迭代器 self.__index 0 return self #每次调用__next__方法会根据当前状态返回下一个元素 def __next__(self): #如果迭代器当前状态超出范围那就抛出StopIteration异常 if self.__index len(self.__addr): raise StopIteration #获取返回内容 val self.__addr[self.__index] #更新迭代器状态指针位置 self.__index 1 return val #Person的实例对象即是可迭代器对象 又是 迭代器 p1 Person(张三, 18, 男, 上海) for item in p1: print(item) for item in p1: print(item) it iter(p1) for item in it: print(item) #这里也会打印值 for item in it: print(item)进阶迭代器玩的就是__next__。看需求实现__next__方法。比如将变量元素全部转为大写将数字转为中文的__next__方法的实现。迭代器的优势迭代器是惰性计算不会一次性生成所有结果所以可以显著降低内存占用。当数据量很大不确定用多少结果时推荐使用迭代器。使用迭代器时返回的只是一个迭代器对象数据还没有生成只有在调用__next__方法的时候才生成了数据。#使用迭代器实现斐波那契数列 class Fibo: def __init__(self, total): self.total total self.index 0 self.prev 1 self.curr 1 def __iter__(self): return self def __next__(self): if self.index self.total: raise StopIteration if self.index 1: num 1 else: num self.prev self.curr self.prev self.curr self.curr num self.index 1 return num #f1是一个迭代器也是一个可迭代器对象 #此时还没有生成数据只是返回了一个对象 f1 Fibo(10) #在调用for循环时调用__next__才生成数据 for item in f1: print(item) # 使用函数实现斐波那契数列 def fibo(total): if total 0: return [] if total 1: return 1 nums [1, 1] for i in range(2, total): nums.append(nums[-1] nums[-2]) return nums #调用函数时数据已经生成了 f2 fibo(10) print(f2)#[1, 1, 2, 3, 5, 8, 13, 21, 34, 55] import tracemalloc tracemalloc.start() f3 Fibo(100000) m3 tracemalloc.get_traced_memory()[1] print(f占用内存{m3 / 1024 / 1024}MB)#占用内存0.00030517578125MB tracemalloc.start() f4 fibo(100000) m4 tracemalloc.get_traced_memory()[1] print(f占用内存{m4 / 1024 / 1024}MB)#占用内存444.99241638183594MB使用迭代器数据没有全部生成而是在调用__next__函数时才生成。生成器生成器函数与生成器对象生成器函数函数体中如果出现了 yield 关键字那该函数是【生成器函数】生成器对象调用【生成器函数】时其函数体不会立刻执行而是返回一个【生成器对象】验证调用生成器函数函数体不会执行而是返回一个生成器对象。注意点不管能否执行到 yield 所在的位置只要函数中有 yield 关键字那该函数就是【生成器函数】如何执行写在【生成器函数】中的代码需要通过【生成器对象】来执行调用【生成器对象】的__next__方法会让【生成器函数】中的代码开始执行。当【生成器函数】中的代码开始执行后遇到 yield 会‘暂停’执行并且其内部会记录‘暂停’位置。后续调用 __next__ 方法时都会从上一次‘暂停’位置继续执行直到再次遇到 yield。遇到 return 会抛出 StopIteration 异常并将 return 后面的表达式作为异常信息。yield 后面的表达式会作为本次 __next__ 方法的返回值。生成器对象的细节生成器对象是一种特殊的迭代器本质是通过 yield 关键字自动实现了迭代器协议。验证生成器对象是迭代器for循环遍历生成器yield也能写在循环里yield from 能把一个【可迭代对象】里的元素依次 yield 出去。替代for yield使用生成器.send(值)可以让生成器继续执行的同时给上一次 yield 传值。next 只能取值send既能取值也能送值。第一次启动生成器不能传值因为第一次没有上一个yield。但是可以传None因为等价于next方法。生成器.next() 等价于 生成器.send(None)用生成器实现两个效果主要是利用了 yield 关键字Python就会自动实现迭代器协议。返回一个迭代器(生成器对象)。用生成器实现一个可以使用for循环遍历的Person类函数实现斐波那契数列无论是迭代器还是生成器对象都可以用listtupleset等直接拿到其里面的内容注意元素多可能挤爆内存生成器虽然实现某些需求代码量比迭代器少因为自动实现了迭代器协议。但是当有自定义需求时还是得通过迭代器来实现即自定义实现__next__方法。生成器表达式一种用类似列表推导式的语法快速创建生成器对象的方式。语法格式(表达式 for 变量 in 可迭代对象)什么时候适合用生成器表达式—— 当“每个结果只依赖当前这一个元素”时。
返回列表