week3 day3 迭代器和生成器

一. 迭代器

我们之前在学习流程控制的时候学习过for循环,那么for循环的内在工作原理是什么呢?今天,我们将先来探讨for循环的工作原理。

迭代器即用来迭代取值的工具,而迭代是重复反馈的活动,其目的通常是为了逼近所需的目标或者结果。每一次对过程的重复称为一次迭代,而每一次迭代得到的结果会作为下一次迭代的初始值,单纯的重复并不是迭代。

while True:
    msg = input('>>: ').strip()
    print(msg)

下述while循环才是一个迭代过程,不仅满足重复,而且以每次重新赋值后的index值作为下一次循环中新的索引进行取值,反复迭代,最终可以取尽列表中的值

goods=['mac','lenovo','acer','dell','sony']

index=0
while index < len(goods):
    print(goods[index])
    index+=1

1.1 什么是迭代器?

迭代器:迭代取值的工具。
什么是迭代:迭代的基础是重复,但是每次重复都是在上一次基础上进行的。

1.2 为何要使用迭代器?

  1. 迭代器提供了一种不依赖于索引的通用的可以取值的方法。
  2. 节省内存。

1.3 如何使用迭代器?

可迭代对象:含有__iter__方法的对象。

当可迭代对象调用__iter__方法的时候,返回一个迭代器对象。

我们所学习的数据类型中,有很多数据类型都有内置的__iter__方法。

''.__iter__()
[].__iter__()
().__iter__()
{}.__iter__()
{}.__iter__()
f=open(r'a.txt',mode='rb')
f.__iter__()
f.__next__()

我们发现字符串,列表,元组,字典,集合文件都有python解释器内置的iter方法,而文件不止有iter方法,还有next方法。

迭代器对象:含有__iter__和__next__方法的对象。

当迭代器对象调用__iter__方法的时候,返回的是它自己。
当迭代器对象调用__next__方法的时候,拿到的是下一个值。
可以一直使用__next__方法取下一个值,直到抛出异常StopIteration。

1.4 迭代器例子

nums=[11,22,33,44,55]
nums_iter=nums.__iter__() or nums_iter=iter(nums) # 拿到迭代器对象
print(nums_iter.__next__()) / print(next(nums_iter))----->11
print(nums_iter.__next__()) / print(next(nums_iter))----->22
print(nums_iter.__next__()) / print(next(nums_iter))----->33
print(nums_iter.__next__()) / print(next(nums_iter))----->44
print(nums_iter.__next__()) / print(next(nums_iter))----->55
print(nums_iter.__next__()) / print(next(nums_iter))----->StopIteration

1.5 for循环的原理

有了迭代器之后,我们便可以不依赖索引迭代取值了,使用while循环的实现方式如下:

goods=['mac','lenovo','acer','dell','sony']
i=iter(goods) #每次都需要重新获取一个迭代器对象
while True:
    try:
        print(next(i))
    except StopIteration: #捕捉异常终止循环
        break

for循环又称迭代循环,in后可以跟任何可迭代对象,上述while循环可以简写为:

goods=['mac','lenovo','acer','dell','sony']
for item in goods:   
    print(item)

for循环在工作时,首先会调用可迭代对象goods内置的iter方法拿到一个迭代器对象,然后再调用该迭代器对象的next方法,将取到的值赋给item,执行循环体完成一次循环,周而复始,知道捕捉StopIteration异常,结束迭代。

1.6 迭代器的优缺点

基于索引的迭代取值,所有迭代状态都保存在了索引中,而基于迭代器实现迭代的方式不再需要索引,所有迭代的状态就保存在迭代器中,然而这种处理方式优点与缺点并存:

1.6.1 迭代器优点
  1. 为序列和非序列类型提供了统一的迭代取值方式。
  2. 惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型数据,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的个数是有限的。
1.6.2 迭代器缺点
  1. 除非取尽,否则无法获取迭代器的长度
  2. 只能取到下一个值,不能回到开始,更像是“一次性的”,迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。

二. 生成器

迭代器的有一个优点是节约内存,但现在我们进行的操作一直是将某个已经占据了内存的数据转换成迭代器对象,然后遍历取值,但是如何才能发挥迭代器节约内存的优点呢?如果我们可以自定义迭代器,那么是不是就可以节约内存呢?

2.1 什么是生成器?

生成器就是自定义的迭代器。

2.2 为何要用生成器?

可以自定义迭代的范围,节省内存。

2.3 怎么使用生成器?

我们之前学过return,return可以让我们拿到函数的返回值,但是同时,函数体遇到return将终止后续代码的运行。因此,需要一个可以拿到返回值,但不会终止函数运行的东西,这就是yield。若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象

def func():
	print(111)
	yield 1
	print(222)
	yield 2
	print(333)
	yield 3
func() # --->空

当函数中出现yield时,因为yield的存在,调用函数不会直接运行函数。但是我们前面说过yield是可以返回值的,因此,我们打印一下运行func()的返回值看一下:

res=func()
print(res) # ---><generator object func at 0x00000231C367C5C8>

我们发现虽然函数体没有运行,但我们得到了一个生成器,我们之前说过生成器就是一个自定义的迭代器,那么迭代器的内置方法生成器一定都有!

func_iter=iter(func())
res1=next(func_iter)----->111 # res1拿到的是第一个yield的值。next方法可以运行到第一个yield挂起
print(res1)----->1
res2=next(func_iter)----->222 # res2拿到的是第二个yield的值。next方法可以运行到第二个yield挂起
print(res2)----->2
res3=next(func_iter)----->333 # res3拿到的是第三个yield的值。next方法可以运行到第三个yield挂起
print(res3)----->3

有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值

2.4 yield表达式的应用

在函数内可以采用表达式形式的yield:

def eater():
	print('Ready to eat')
	while True:
		food=yield
		print('get the food: %s, and start to eat' %food)

可以拿到函数的生成器对象持续为函数体send值(给yield传值),如下:

>>> g=eater() # 得到生成器对象
>>> g
<generator object eater at 0x101b6e2b0>
>>> next(e) # 需要事先”初始化”一次,让函数挂起在food=yield,等待调用g.send()方法为其传值
Ready to eat
>>> g.send('包子') # 给yield传值
get the food: 包子, and start to eat
>>> g.send('鸡腿') # 给yield传值
get the food: 鸡腿, and start to eat

针对表达式形式的yield生成器对象必须事先被初始化一次,让函数挂起在food=yield位置,等待调用g.send(value)方法为函数体传值,g.send(None)等同于next(g)

我们可以编写装饰器来完成为所有表达式形式yield对应生成器的初始化操作,如下:

def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs)
        next(g)
        return g
    return wrapper

@init
def eater():
    print('Ready to eat')
    while True:
        food=yield
        print('get the food: %s, and start to eat' %food)

表达式形式的yield也可以用于返回多次值,即变量名=yield值的形式,如下:

>>> def eater():
...     print('Ready to eat')
...     food_list=[]
...     while True:
...         food=yield food_list
...         food_list.append(food)
... 
>>> e=eater()
>>> next(e)
Ready to eat
[]
>>> e.send('蒸羊羔')
['蒸羊羔']
>>> e.send('蒸熊掌')
['蒸羊羔', '蒸熊掌']
>>> e.send('蒸鹿尾儿')
['蒸羊羔', '蒸熊掌', '蒸鹿尾儿']
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值