Python迭代器与生成器
一、迭代器和生成器的定义
可迭代对象并不是指某一个数据类型,而是特指存储了元素的一个容器对象。这里的容器对象可以具象为:列表、字典、元组、字符串、range都算是一个"容器"。可迭代对象有个方法叫_iter_()方法,翻译过来叫做迭代,正是因为这个方法才让可迭代对象成为了可迭代对象。纯可迭代对象的内部数据"所见即所得",可迭代的数据是已经看得见的数据了。
首先迭代器肯定是一个可迭代对象,迭代器拥有迭代对象的所有特征。迭代器同时拥有__iter__()和__next__()方法。迭代器可以记住遍历的位置(for 的工作核心之一)。迭代器是一个惰性机制,何为惰性。不叫我动,我不动,叫我,我才动。
生成器肯定是一个迭代器,也是一个可迭代对象,一边迭代一边生成数据。生成器有__iter__()和__next__()还有一个yield关键字/命令(类似return),生成器之所以能生成是因为保存了一套算法/逻辑可以持续生成数据,yield返回一个值,但是不会结束函数,会记住当前值的位置。
常规的可迭代对象是一口气给你所有的数据,无论你是否需要,可迭代对象总是要给你他拿到的所有,这样会随着迭代对象数据的增加消耗巨大资源。
迭代器是按需供应的机制,当需要数据的时候,迭代器会帮你取数据。迭代器是一个单向的阀门,只能前进不能后退。
生成器也是按需供应的机制,只需要赋给规则,按照规则生成数据。生成器的优势在于节省了内存或者说运算资源。
可迭代对象
1 2 3 4 5 6 7 | # 可迭代对象 str = 'abc' dic = { 'name' : 'James' , 'age' : 30 } lis = [ 1 , 2 , 3 ] tup = { 'c' , 'a' , 'b' } for x in str : print (x) |
a
b
c
二、声明/使用迭代器
严格的说迭代器不是直接声明的而是从常规可迭代对象转换过来的。
STEP1:先有一个序列/集合数据(可迭代对象)
STEP2:再使用iter方法转换为迭代器
STEP3:可以使用next()函数或者内置特殊方法__next__()以"惰性"输出/获取迭代器里的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | l_data = [ 1 , 2 , 3 , 4 , 5 ] new_data = iter (l_data) print (new_data) print ( next (new_data)) print (new_data.__next__()) print (new_data.__next__()) print ( next (new_data)) print ( next (new_data)) new_iter = iter (l_data) #直接打印迭代器无法获取到数据,这个跟可迭代对象有差别 print ( type (new_iter)) print (new_iter) #可迭代对象转迭代器不会进行同id引用,迭代器会重新建立一个新对象 print ( id (new_iter)) print ( id (l_data)) #通过__iter__()数据类型/对象的魔法方法/特殊方法 转换成迭代器 new_magic = l_data.__iter__() print ( id (l_data)) #通过next()提取数据 Python内置方法/函数 print ( next (new_magic)) print ( next (new_magic)) print ( next (new_magic)) #通过__next__提取数据 Python内置方法/函数 print (new_iter.__next__()) print (new_iter.__next__()) print (new_iter.__next__()) |
<list_iterator object at 0x000001BA2BD27160>
<class 'list_iterator'>
<list_iterator object at 0x000001BA2BD242E0>
1899110744800
1899124009664
1899124009664
1
2
3
1
2
3
特别注意:
1、通过iter()转换成迭代器后,迭代器并不会引用l_data而是直接创建自己的对象
2、注意迭代器里的元素取按照顺序取出,取一个少一个,直到取完为止(类似库存)
3、迭代器内元素取完以后输出错误信息Stoplteration
三、在for循环中,可迭代对象和迭代器的不同表现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | l_data = [ 1 , 2 , 3 , 4 , 5 ] for i in l_data: print (i,end = ' ' ) print () for i in l_data: print (i,end = ' ' ) print () for i in l_data: print (i,end = ' ' ) print () new_data = iter (l_data) for i in l_data: print (i,end = ' ' ) #无输出,也不会报错 #因为第一个for把数据都用完了,第二次循环等于在循环一个列表 |
1 2 3 4 5 6 7 8 9 10 | #首先理解一下,for循环迭代器的时候,只能循环一次,再循环就没有数据了 #WHY 因为迭代器是一个单向的,一个仓库,取完了就没了 #第二for 实际上就等于在遍历/循环一个空列表了 l_data = [ 1 , 2 , 3 , 4 , 5 ] new_magic = l_data.__iter__() for x in new_magic: print (x) #那么为什么for不会因为数据没有了导致的报错StopIteration #因为for会自动处理这个异常 |
特别注意:
1、for循环每次从迭代器中取数据也是一次一次的next,但是for不会因为取完数据而报错。其实不是不报错而是把错误给屏蔽掉了。
2、迭代器是一个单向数据获取并减库的机制,取完了就没有了,与之类比的就是可迭代对象,每次使用都是从头给你来过一次。
四、声明/使用一个生成器
最简单的生成器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | # 最简单的生成器 #变量引用对象 def gen_num(): yield 1 yield 2 yield 3 yield 'Python' yield 4 yield 'hello' #变量引用对象 my_gen1 = gen_num() print ( next (my_gen1)) #调用一次,返回一个 print ( next (my_gen1)) #再次调用,再返回 print ( next (my_gen1)) #以此类推 print ( next (my_gen1)) #以此类推 print ( next (my_gen1)) print ( next (my_gen1)) #以逻辑输出生成器 #变量引用对象 def gen_num(): n = [ 1 , 2 , 3 ] for i in n: #边循环边计算/生成数据 yield i my_gen2 = gen_num() print ( next (my_gen2)) print ( next (my_gen2)) print ( next (my_gen2)) #print(next(my_gen2)) |
1
2
3
Python
4
hello
1
2
3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # 最简单的生成器 #变量引用对象 def gen_num(): yield 1 yield 2 yield 3 yield 'Python' yield 4 yield 'hello' #变量引用对象 my_gen1 = gen_num() for x in my_gen1: print (x) #以逻辑输出生成器 #变量引用对象 def gen_num(): n = [ 1 , 2 , 3 ] for i in n: #边循环边计算/生成数据 yield i my_gen2 = gen_num() print ( next (my_gen2)) print ( next (my_gen2)) print ( next (my_gen2)) #print(next(my_gen2)) |
1
2
3
Python
4
hello
1
2
3
传统函数与yield后的函数比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #传统函数 def hello(): print ( 'Hello,python' ) return "完成" print ( "我是return后的小兄弟" ) #这句话不会执行 print (hello()) #生成器里的yield def gen_num(): n = 0 while True : yield n n = n + 1 print ( "我是yield后的小兄弟" ) my_num1 = gen_num() next (my_num1) |
1 | print ( next (my_gen2)) |
打印结果如下,每次执行后会返回到yield的部分继续执行,每次增加1
1 2 3 4 5 6 7 8 9 | #生成器里的yield def range_number(start,end): n = start while n<end: yield n n = n + 1 print ( "我是yield后的小兄弟" ) nums = range_number( 10 , 20 ) next (nums) |
当指定范围时,n从10开始往后叠加
当20的时候结束,可以指定生成器中yield数据的范围
特别注意:
1、return命令是函数的"终结者",遇到return函数结束
2、yield是生成器的标志之一,是使用的时候再进行计算返回结果,每次返回记录上一次的位置。下一次接着来。
以推导声明生成器
生成器的创建除了可以在def函数里使用yield构建以外还可以支持一种构建方式为推导模式
通过元组+推导式构建生成器
1 2 3 4 5 6 7 8 9 10 11 12 13 | #一个简单的推导列表 my_list = [x for x in range ( 10 )] print (my_list) #一个基于推导的生成器 my_gen1 = (x for x in range ( 10 )) print (my_gen1) print ( next (my_gen1)) #等同于推导的生成器 def my_gen2(): for x in range ( 10 ): yield x print (my_gen2) print ( next (my_gen2())) |
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
<generator object <genexpr> at 0x000001BA2C981CB0>
0
<function my_gen2 at 0x000001BA2C856AC0>
0
生成器和迭代器的数据还原
无论是迭代器还是生成器数据,如何全部提出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #可迭代对象 l_data = [ 1 , 2 , 3 , 4 , 5 ] #声明迭代器 new_data = iter (l_data) #全部提取 my_data = list (new_data) print (my_data) #声明生成器 my_gen = (x for x in range ( 10 )) #全部提取 my_data = list (my_gen) print (my_data) print ( type (my_data)) print ( type (my_gen)) |
输出结果:
[1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
<class 'list'>
如果已经建立了可迭代对象,然后转迭代器是不是一样占用内存
1 2 3 4 5 6 7 8 | n_data = [ 1 , 2 , 3 , 4 ] new_data = iter (n_data) #这里做一个删除动作,释放n_data的内存 del n_data # 看看n_data是否存在-----n_data原始数据已经被删除了 #print(n_data) #迭代器会重新建立一个对象,一个迭代器对象,删除n_data后,迭代器依然存在 print (new_data) |
输出结果:
<list_iterator object at 0x000001BA2BD268F0>
迭代器、生成器实例
作业背景:
年会、答谢会、同学会总之各种会为了营造气氛,都会有一个你我他都喜欢的环节那就是抽奖,那么今天我们就来做一个抽奖游戏。要使用迭代器、生成器来完成。
1、拟定一个奖品列表,奖品自定义(iPad iphoneX ...)
2、每次抽奖后需要从列表中拿掉一个奖品(使用迭代器实现)
3、拟定一个抽奖概率不是每次都能抽到奖品(活用random函数+list配合)
4、中奖后需要给中奖人分配一个id,规则按照0001-000X(使用生成器实现)
注意事项
1、中奖则生成一个中奖id,不中奖当然就不需要了
2、中奖则从库中减少一个奖品(这里我们定义为一种奖品就一个,不做单奖品多数量)
3、注意如果奖品用尽(迭代器)则会报错,这里需要想办法屏蔽报错。
输出要求:
抽一次奖,输出一次中奖状态,可以包含中奖和不中奖两个状态,如果中奖则需要输出奖品和中奖id
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | import random # 使用一个生成器生成抽奖ID my_gen = (x for x in range ( 1 , 10000 )) #参加抽奖的总人数(注意这里是延伸的一个设计) total_p = 30 #奖品字典 包含奖品和奖品数量 prize_dict = { 'iphone11' : 2 , 'ipad' : 3 , 'macbook' : 1 , 'switch' : 3 , '京东500元购物卡' : 5 , 'PS4' : 2 } #用于抽奖的列表,我们叫做奖池,奖池一开始肯定是空的,根据我们的实际情况添加 lottery_list = [] #抽奖概率的主控函数,并且可以改变中奖概率大小 def probability(num_p,dic): ''' num_p:公司参与抽奖的总人数 dic: 奖品字典,包括奖品名称和奖品数量 ''' # 这里做一级输入数据的判断,让控制函数更健壮一些 if type (num_p)! = int or type (dic)! = dict : print ( '您输入的参数格式不正确!' ) else : #主控逻辑 sum_dic = 0 #------------先遍历奖品字典--------------- for k,v in prize_dict.items(): #根据字典中各个奖品的数量,将奖品添加到抽奖池列表中 #改变奖品数量可以改变中奖概率(奖品越多中奖概率越高) for i in range (v): lottery_list.append(k) sum_dic + = v #注意这里要计算一下 总奖品数量 for j in range (num_p - sum_dic): #根据参与抽奖总人数往抽奖池中添加数据,将总人数减去奖品数 # 得到的数字就是往奖品池列表添加的不中奖数量,改变人数可以改变中奖概率 # 不中奖也是一种奖品,因为这个概率取决于奖品数量和人数的差值 # 差值越大不中奖概率越大 lottery_list.append( '没中奖' ) #这种设计逻辑,如果你有30人参加 #30人都会抽奖,那么保证你的奖池抽干,每个人都有一个奖品 help (probability) #各就各位,调用函数,传入参数,准备迭代器! #调用函数,传参 probability(total_p,prize_dict) #打乱抽奖池里面的元素的顺序(这个特别重要,添加是按照顺序添加,也是按照顺序取值,所以需要打乱) #这个就是***摇晃***抽奖池的过程 random.shuffle(lottery_list) print (lottery_list) #将已经打乱顺序的抽奖池列表转换为迭代器开始迭代抽奖 new_iter = iter (lottery_list) # 注意取得时候是从下标index/索引 0开始取值,不是从-1开始 print ( next (new_iter)) #按照众多抽奖的习惯之一 一人一抽模式 while 1 : if total_p! = 0 : print ( '-' * 50 ) print ( "开始抽奖请输入'抽奖',结束抽奖请输出'结束'" ) if input () = = '抽奖' : p = next (new_iter) total_p - = 1 #抽一次减少一次抽奖次数 if p = = '没中奖' : print ( '很遗憾您没有中奖' ) else : num = format ( next (my_gen), '04d' ) #特别注意一下,需要通过format()函数转一下,04 print ( '恭喜您中奖了' + p + ',中奖ID是:' + str (num)) elif input () = = '结束' : break else : print ( '抽奖次数已用完' ) break |
输出结果为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import random #初始化一个中奖概率,切记里面的数字加起来等于100,代表每一个奖项的概率 init_probability = { 'ipad' : 70 , 'iphone' : 10 , 'macpro' : 10 , '没中奖' : 10 } #奖品库存,这个是数量/库存 prize_stock = { 'ipad' : 10 , 'iphone' : 5 , 'macpro' : 6 } #创建实际需要使用的概率表 probability_list = [k for k,v in init_probability.items() for x in range (v)] #打乱顺序 random.shuffle(probability_list) print (probability_list) my_prize = random.choice(probability_list) prize_stock[my_prize] = prize_stock[my_prize] - 1 print (prize_stock) |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)