Python迭代器与生成器
一、迭代器和生成器的定义
可迭代对象并不是指某一个数据类型,而是特指存储了元素的一个容器对象。这里的容器对象可以具象为:列表、字典、元组、字符串、range都算是一个"容器"。可迭代对象有个方法叫_iter_()方法,翻译过来叫做迭代,正是因为这个方法才让可迭代对象成为了可迭代对象。纯可迭代对象的内部数据"所见即所得",可迭代的数据是已经看得见的数据了。
首先迭代器肯定是一个可迭代对象,迭代器拥有迭代对象的所有特征。迭代器同时拥有__iter__()和__next__()方法。迭代器可以记住遍历的位置(for 的工作核心之一)。迭代器是一个惰性机制,何为惰性。不叫我动,我不动,叫我,我才动。
生成器肯定是一个迭代器,也是一个可迭代对象,一边迭代一边生成数据。生成器有__iter__()和__next__()还有一个yield关键字/命令(类似return),生成器之所以能生成是因为保存了一套算法/逻辑可以持续生成数据,yield返回一个值,但是不会结束函数,会记住当前值的位置。
常规的可迭代对象是一口气给你所有的数据,无论你是否需要,可迭代对象总是要给你他拿到的所有,这样会随着迭代对象数据的增加消耗巨大资源。
迭代器是按需供应的机制,当需要数据的时候,迭代器会帮你取数据。迭代器是一个单向的阀门,只能前进不能后退。
生成器也是按需供应的机制,只需要赋给规则,按照规则生成数据。生成器的优势在于节省了内存或者说运算资源。

可迭代对象
# 可迭代对象
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__()以"惰性"输出/获取迭代器里的值
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循环中,可迭代对象和迭代器的不同表现
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把数据都用完了,第二次循环等于在循环一个列表
#首先理解一下,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、迭代器是一个单向数据获取并减库的机制,取完了就没有了,与之类比的就是可迭代对象,每次使用都是从头给你来过一次。
四、声明/使用一个生成器
最简单的生成器
# 最简单的生成器
#变量引用对象
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
# 最简单的生成器
#变量引用对象
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后的函数比较
#传统函数
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)
print(next(my_gen2))
打印结果如下,每次执行后会返回到yield的部分继续执行,每次增加1


#生成器里的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构建以外还可以支持一种构建方式为推导模式
通过元组+推导式构建生成器
#一个简单的推导列表
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
生成器和迭代器的数据还原
无论是迭代器还是生成器数据,如何全部提出来
#可迭代对象 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'>
如果已经建立了可迭代对象,然后转迭代器是不是一样占用内存
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
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
输出结果为:

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)

浙公网安备 33010602011771号