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、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)
posted @   leagueandlegends  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
点击右上角即可分享
微信分享提示