当前位置: 首页 > news >正文

【Python】保姆级新手教程------第 11 章 迭代器 vs 生成器

第 11 章 迭代器 vs 生成器

迭代器

先区分两个概念:可迭代对象(iterable)、迭代器(iterator)

1️⃣可迭代对象(iterable)

概念:能被 for 循环遍历的对象,就是可迭代对象(iterable)

如下这些都是可迭代对象(iterable):

names=['张三','李四','王五']citys=('北京','上海','深圳')msg='hello'foriteminnames:print(item)

如下这些不是可迭代对象(iterable):

age=10deftest():passforitemintest:print(item)endregion

可迭代对象都拥有__iter__方法。

names=['张三','李四','王五']citys=('北京','上海','深圳')msg='hello'age=10deftest():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=('北京','上海','深圳')msg='hello'print(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=['张三','李四','王五']it=iter(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)it=iter(names)# 2️⃣开启一个无限循环whileTrue:try:# 3️⃣调用__next__方法,获取下一个元素item=next(it)print(item)exceptStopIteration:# 4️⃣捕获 StopIteration 异常,随后结束循环break

迭代器(iterator)也拥有__iter__方法,并且其返回值是迭代器自身。

这么设计的原因:让 for 循环也能遍历迭代器(即:为了让 iter(迭代器) 不出错)。

names=['张三','李四','王五']it=iter(names)print(it)result=iter(it)print(result)x=iter(result)print(x)it=iter(names)foriteminit:print(item)

:::info
迭代器协议:一个对象如果同时满足如下规范,那该对象就是一个迭代器:

  1. 能被iter()接受。
  2. 能被next()一步一步取值。

:::

迭代器是一次性的,状态只会向前推进,且不会自动重置(迭代器在遍历的过程中会被“消耗”)。

names=['张三','李四','王五']it1=iter(names)it2=iter(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.name=name self.age=age self.gender=gender self.address=addressdef__iter__(self):returnPersonIterator(self)classPersonIterator:def__init__(self,p):# 将外部传进来的数据保存好self.p=p# 设置迭代器的初始化状态(指针位置)self.index=0# 配置好要遍历的内容self.attrs=[p.name,p.age,p.gender,p.address]# 迭代器的__iter__方法会返回迭代器自身def__iter__(self):returnself# 每次调用__next__方法,会根据当前的状态,返回下一个元素def__next__(self):# 如果指针的位置超出范围,那就抛出StopIteration异常ifself.index>=len(self.attrs):raiseStopIteration# 获取要返回的内容value=self.attrs[self.index]# 更新迭代器状态(指针位置)self.index+=1# 返回valuereturnvalue# 目标:p1=Person('张三',18,'男','北京昌平')foriteminp1:print(item)foriteminp1:print(item)

实现方式2️⃣

classPerson:def__init__(self,name,age,gender,address):self.name=name self.age=age self.gender=gender self.address=address# 设置迭代器的初始化状态(指针位置)self.__index=0# 配置好要遍历的内容self.__attrs=[name,age,gender,address]def__iter__(self):self.__index=0returnselfdef__next__(self):# 如果指针的位置超出范围,那就抛出StopIteration异常ifself.__index>=len(self.__attrs):raiseStopIteration# 获取要返回的内容value=self.__attrs[self.__index]# 更新迭代器状态(指针位置)self.__index+=1# 返回valuereturnvalue# 目标:# 下面的p1既是可迭代对象,又是迭代器p1=Person('张三',18,'男','北京昌平')foriteminp1:print(item)foriteminp1:print(item)

进阶:迭代器玩的就是__next__

fromcn2animportan2cnclassPerson:def__init__(self,name,age,gender,address):self.name=name self.age=age self.gender=gender self.address=address# 设置迭代器的初始化状态(指针位置)self.__index=0# 配置好要遍历的内容self.__attrs=[name,age,gender,address]def__iter__(self):self.__index=0returnselfdef__next__(self):# 如果指针的位置超出范围,那就抛出StopIteration异常ifself.__index>=len(self.__attrs):raiseStopIteration# 获取要返回的内容value=self.__attrs[self.__index]# 将字符串转为大写ifisinstance(value,str):value=value.upper()# 将数字转为汉语形式ifisinstance(value,int):value=an2cn(value)# 更新迭代器状态(指针位置)self.__index+=1# 返回valuereturnvalue# 目标:# 下面的p1既是可迭代对象,又是迭代器p1=Person('zhangsan',18,'男','北京昌平')foriteminp1:print(item)

4️⃣迭代器的优势

  1. 迭代器是惰性计算,不会一次性生成所有结果,所以能显著降低内存占用。
  2. 当数据量很大,不确定要用多少结果时,推荐使用迭代器。

使用迭代器实现【斐波那契数列】:

classFibo:def__init__(self,total):# 要生成多少个数self.total=total# 当前生成到第几个了(计数器,指针)self.index=0# 初始的两个值self.pre=1self.cur=1def__iter__(self):returnselfdef__next__(self):# 当生成足够数量后,抛出StopIteration异常ifself.index>=self.total:raiseStopIteration# 前两项都是1ifself.index<2:value=1else:# 新的结果等于前两项的和value=self.pre+self.cur# 更新一下pre和curself.pre=self.cur self.cur=value# 计数器+1self.index+=1# 返回valuereturnvalue

不使用迭代器实现【斐波那契数列】:

deffibo(total):iftotal<=0:return[]iftotal==1:return[1]nums=[1,1]foriinrange(2,total):nums.append(nums[-1]+nums[-2])returnnums

分析内存占用情况:

分别运行如下两段代码后,会发现迭代器实现明显节约内存。

tracemalloc.start()f1=Fibo(0)m=tracemalloc.get_traced_memory()[1]print(f'内存占用是:{m/1024/1024}MB')
tracemalloc.start()f1=fibo(0)m=tracemalloc.get_traced_memory()[1]print(f'内存占用是:{m/1024/1024}MB')

生成器

1️⃣两个概念

  1. 生成器函数:函数体中如果出现了yield关键字,那该函数是『生成器函数』。
  2. 生成器对象:调用『生成器函数』时,其函数体不会立刻执行,而是返回一个『生成器对象』。

备注:不管能否执行到yield所在的位置,只要函数中有yield,那该函数就是『生成器函数』。

defdemo():print('demo函数开始执行了')print(100)yielda=200print(a)d=demo()print(d)

2️⃣几个细节

写在『生成器函数』中的代码,需要通过『生成器对象』来执行:

:::info

  1. 调用『生成器对象』的__next__方法,会让『生成器函数』中的代码开始执行。
  2. 当『生成器函数』中的代码开始执行后,遇到yield会“暂停”,并会记录“暂停”的位置。
  3. 后续调用__next__方法时,都会从上一次“暂停”的位置,继续运行,直到再次遇到 yield。
  4. 遇到return会抛出StopIteration异常,并将return后面的表达式,作为异常信息。
  5. yield后面所写的表达式,会作为本次__next__方法的返回值。

:::

defdemo():print('demo函数开始执行了')print(100)yield'我是第1个yield所返回的数据'a=200print(a)yield'我是第2个yield所返回的数据'b=300print(b)return'尚硅谷'd=demo()r1=next(d)print(r1)r2=next(d)print(r2)try:next(d)exceptStopIterationase:print(e)

生成器对象是一种特殊的迭代器(本质是通过yield自动实现了迭代器协议)

defdemo():print('demo函数开始执行了')print(100)yield'我是第1个yield所返回的数据'a=200print(a)yield'我是第2个yield所返回的数据'b=300print(b)return'尚硅谷'd=demo()# 验证:生成器对象d,和迭代器一样,也拥有:__iter__ 和 __next__ 方法print(hasattr(d,'__iter__'))print(hasattr(d,'__next__'))# 验证:生成器对象的__iter__方法,和迭代器一样,返回的也是自身result=iter(d)print(result==d)# for循环遍历生成器foritemind:print(item)# for循环背后的逻辑gen=iter(d)whileTrue:try:value=next(gen)print(value)exceptStopIteration:break

yield也能写在循环里

regiondefcreate_car(total):forindexinrange(1,total+1):yieldf'我是第{index}台车'# cars是生成器对象cars=create_car(5)# 调用一次cars的__next__方法,就会得到一台车c1=next(cars)print(c1)c2=next(cars)print(c2)c3=next(cars)print(c3)c4=next(cars)print(c4)c5=next(cars)print(c5)forcarincars:print(car)endregion

yield from能把一个『可迭代对象』里的东西依次yield出去。(替代:for + yield)

defdemo():nums=[10,20,30,40]yieldfromnums d=demo()r1=next(d)print(r1)r2=next(d)print(r2)r3=next(d)print(r3)r4=next(d)print(r4)foritemind:print(item)

使用:生成器.send(值)可以让生成器继续执行的同时,给上一次yield传值。

备注1:next只能取值,send既能取值,也能送值。

备注2:第一次启动生成器,不能传值!(或者说只能传 None 值)

defdemo():print('demo函数开始执行了')print(100)a=yield'我是第1个yield所返回的数据'print(a)b=yield'我是第2个yield所返回的数据'print(b)return'尚硅谷'd=demo()r1=next(d)# 此处等价于 d.send(None)print(r1)r2=d.send(666)print(r2)try:d.send(888)exceptStopIterationase:print(e)

3️⃣生成器的应用

用生成器实现遍历Person类的实例对象:

classPerson:def__init__(self,name,age,gender,address):self.name=name self.age=age self.gender=gender self.address=address self.__attr=[name,age,gender,address]def__iter__(self):# yield self.name# yield self.age# yield self.gender# yield self.addressyieldfromself.__attr p1=Person('张三',18,'男','北京昌平')# 目标:forattrinp1:print(attr)

用生成器实现斐波那契数列:

deffibo(total):pre=1cur=1forindexinrange(total):ifindex<2:yield1else:value=pre+cur pre=cur cur=valueyieldvalue f1=fibo(10)foriteminf1:print(item)

无论是迭代器,还是生成器对象,都可以用listtupleset等直接拿到其里面的所有内容(注意:如果数据量很大,可能会挤爆内存)

deffibo(total):pre=1cur=1forindexinrange(total):ifindex<2:yield1else:value=pre+cur pre=cur cur=valueyieldvalue f1=fibo(10)result=set(f1)print(result)

4️⃣生成器表达式

生成器表达式:一种用类似列表推导式的语法,快速创建生成器对象的方式。

语法格式:(表达式 for 变量 in 可迭代对象)

什么时候适合用生成器表达式?———— 当“每个结果,只依赖当前这一个元素”时。

nums=[10,20,30,40]# 列表推导式result1=[n*2forninnums]print(result1)# 生成器表达式(和列表推导式很像,不要搞混)result2=(n*2forninnums)foriteminresult2:print(item)
http://www.jsqmd.com/news/986156/

相关文章:

  • 2026年靠谱护墙板工厂挑选指南
  • Linux 重命名命令(小白版,一看就会)
  • 国家级!工信部+国资委联合出手:人形机器人万台级落地,具身智能进入“作业模式“
  • 以太网型温湿度传感器通讯协议手册(含指令实例)
  • 破解多头批量焊接痛点:冈兴多头点焊机的4C柔性精准焊接解决方案 - 速递信息
  • 双减背景下本土 K12 教培生存研究 —— 以周浦圣杰教育本地化教研为例
  • 水性聚氨酯地坪施工怎么做?环保耐磨地坪解决方案——港珠澳大桥人工岛地坪施工商 - 热点速览
  • 翡翠镶嵌靠谱定制服务商推荐选择评测 - 奔跑123
  • 2026武汉苹果手机维修性价比推荐:修得好还花得值,这家专业品牌的技术账
  • AI写论文新选择!4款AI论文生成工具,为期刊论文写作提效!
  • 【Qt/C++ 桌面开发实战营】第1篇:Qt环境搭建与第一个Hello World
  • 马斯克都在看的重庆东站,这5个机位,拍完根本走不动路 - 资讯焦点
  • FP5207/FP7208 在单节电池升压 12V 30W 大功率升压芯片选型区别
  • AI商业化难点:从模型选择到执行权让渡,Agent如何破局?
  • 一体化闸门远程控制系统详解:架构、核心功能与应用场景
  • 效率提升40% vs 人工流程:从三大维度看ONEKEY的工程化优势
  • 2026伊春防水补漏哪家靠谱?正规公司排名及避坑价格指南 - 苏易修缮
  • 0 代码也能搭 Agent:我用做了一个企业售前知识助手
  • Win11 BitLocker 怎么关掉?设置、命令提示符和 PowerShell 三种关法都给你
  • 618囤货省钱指南,京东PLUS1888元超级补贴及88VIP九折券领取中,联动618红包多重让利 - 资讯焦点
  • 【鸿蒙】ArkUI 列表性能优化:LazyForEach 与组件复用深度解析
  • 如何在HyperMesh的两片相邻体单元间批量创建RBE3实现载荷传递
  • 2026四平防水补漏哪家靠谱?正规公司排名及避坑价格指南 - 苏易修缮
  • Visual Studio 2022项目中的.sln是什么?
  • 2026太原防水补漏哪家靠谱?正规公司排名及避坑价格指南 - 苏易修缮
  • 2026新疆旅游避坑|真实靠谱本地持证导游精选推荐(纯玩无套路) - 盛世西域旅行
  • 温州上班族必看!2026学历提升深度测评:它的AI助学+双师课堂到底有多强?
  • 机器人二次开发机器人动作定制?高保真动作迁移
  • 2026年长三角冷冻式干燥机厂家实力盘点:工业气体净化核心供应商推荐 - 资讯速览
  • 2026怒江权威认证贵金属回收 TOP5+黄金回收白银回收铂金回收门店地址电话推荐