python 基础:迭代器、生成器

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

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

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

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

上面这个迭代方法只适合序列类型,并不适合字典、集合、文件对象的迭代取值。
有人会说for循环可以对字典、文件对象、集合进行循环取值,其实for循环用到的是迭代器,因此这里没有用for循环为例说明,后面会具体说迭代器和可迭代对象的区别与联系。

(1)可迭代对象
通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串,列表,元组。对于没有索引的字典、集合、文件对象等非序列类型,必须找到一种不依赖索引来进行迭代取值的方式,这就用到了迭代器。

可迭代对象(Iterable)。从语法形式上讲,内置有__iter__方法的对象都是可迭代对象,字符串、列表、元组、字典、集合、打开的文件都是可迭代对象。

{'name':'吴晋丞'}.__iter__
{7,8,9}.__iter__
……

(2)迭代器对象
调用obj.iter()方法返回的结果就是一个迭代器对象(Iterator)。
除文件对象外,字典、集合、列表等都只是可迭代对象而已,它们可以通过调用iter方法生成迭代器对象,并进行迭代取值。但文件对象在python中使用时,直接生成的就是迭代器对象,当然它也是可迭代对象。文件对象可以直接调用next函数,只有迭代器对象才能调用next方法,因此文件对象是迭代器对象。执行迭代器对象.iter()方法得到的仍然是迭代器本身,而执行迭代器对象.next()方法就会计算出迭代器中的下一个值。

>>> s={1,2,3} # 可迭代对象s
>>> i=iter(s)  # 本质就是在调用s.__iter__(),返回s的迭代器对象i,
>>> next(i) # 本质就是在调用i.__next__()
1
>>> next(i)
2
>>> next(i)
3
>>> next(i)  #抛出StopIteration的异常,代表无值可取,迭代结束,要想继续取值,除非在重新生成一个迭代器。

为什么文件对象要直接设置成迭代器对象,集合、字典等不设置成迭代器对象呢?

答:首先我们应该明白迭代器这种需要一个值,next方法取一个值,这样是降低了内存的消耗的。集合、字典等定义下来,本来就不是用来存很多、很多东西的。但是一个文件对象却可以很大很大,因此必须直接生成迭代器对象,控制内存的消耗。

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

dic = {'a':1,'b':2,'c':3}
d_iterator = iter(dic) #d_iterator便是一个迭代器对象,iter方法生成的就是一个迭代器对象
while True: # while循环的循环体是进行循环迭代取值的
    try:
        print(next(d_iterator))
    except(StopIteration): # 捕获异常
        break # 异常处理

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

dic = {'a':1,'b':2,'c':3}
for i in dic:
	print(item)

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

为什么文件对象中也需要有iter方法呢?
它不是已经是迭代器对象了吗,而且前面说迭代器调用iter方法屁用没有,得到的还是本身。

答:这是一个专门为统一for循环而设计的,因为for循环中你不确定自己in后面到底是不是迭代器对象,难道还要判断一下吗?那不是太麻烦了,无论谁来,只要可迭代,我就给你iter方法一下,即使是文件对象,也不影响啥。所以文件对象也需要一个iter方法,拿去让for循环调用。

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

(1)优点
为序列和非序列类型提供了一种统一的迭代取值方式。
惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。

(2)缺点
除非取尽,否则无法获取迭代器的长度
只能取下一个值,不能回到开始,更像是'一次性的',迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。

二、生成器
1、生成器与yield
若函数体包含yield关键字,再调用函数,并不会执行函数体代码,只是得到的返回值即生成器对象

def my_range(start,stop,step=1):
    while start < stop:
        yield start
        start += step
g = my_range(1,10,2)
print(g)
while True:
    try:
        print(g.__next__()) #yield用于返回值
    except(StopIteration):
        break

执行结果:

生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器
有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值
2、yield表达式应用
(1)yield基本应用
在函数内可以采用表达式形式的yield

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

可以拿到函数的生成器对象持续为函数体send值,如下

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

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

(2)装饰器初始化生成器

def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs) # 函数中有关键字yield,因此函数不会执行,而是直接返回一个生成器对象
        next(g) # 这步必须要做,执行函数体代码,并让eater函数挂起在food = yield处
        return g # 返回生成器对象,让用户能够调用,当然这也是装饰器必要的返回值伪装。
    return wrapper

@init
def eater():
    print('Ready to eat')
    food_list = []
    while True:
        food = yield food_list #左边send传值,右边next或send返回值
        food_list.append(food)
g = eater()
g.send('阆中牛肉面')
g.send('成都冒菜')
g.send('成都羊肉汤锅')
res = g.send('酸辣粉')
print(res)

原文地址:https://www.cnblogs.com/Eric-6688/p/13975662.html