首页 > 代码库 > python迭代器和生成器

python迭代器和生成器

心情有点复杂,同学和几个发小这几天都相继做了爸爸,手段都很低级,肚子搞大,唉~而我确还在组建家庭的路上,我不想太急,家里毕竟不能为我提供更多的帮助,坚持吧!

工作之余还在学习确实很累,想一锹挖个井实在太难,还是得慢慢得来,慢慢得学必定有很多的收获,坚持吧!

上海的天气好热,早上出门挤上地铁全身已湿透,晚上下班铺面而来的全是汽车尾气和热浪,坚持吧!

人生还早,谁能笑到最后呢,坚持吧!

1.迭代器协议

由于生成器自动实现了迭代器协议,我们有必要了解迭代器协议是什么,才能更好的理解生成器。

1)迭代器协议:对象要提供__next()__方法,它要么返回迭代中的下一个对象,要么引起一个StopIteration错误,终止迭代

2)可迭代对象:实现了迭代器协议的对象

3)协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。

for循环的本质:循环所有对象,全都是使用迭代器协议

(字符串,列表,元组,字典,集合,文件对象)这些都不是可迭代对象,只不过在for循环式,调用了他们内部的__iter__方法,把他们变成了可迭代对象

list = [‘a‘,‘b‘,1,2]
# for i in list:
#     print(i)
list_iter = list.__iter__()#调用__iter__方法将list对象变成可迭代对象
print(list_iter.__next__())依次调用,知道引起StopIteration错误
print(list_iter.__next__())
print(list_iter.__next__())
print(list_iter.__next__())

用while循环模拟for循环做的事情
list_iter = list.__iter__()
while True:
try:
print(list_iter.__next__())
except StopIteration:
print(‘迭代完毕,可以终止‘)
break

迭代器对象的本质是一种数据流,它只是一个有序的序列,我们不能提前知道它的长度,只有不断通过__next__()取出下一个值。

Interator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

优点 
     1):迭代器提供了一种不依赖于索引的取值方式,这样就可以遍历那些没有索引的可迭代对象了(字典,集合,文件) 
   2):迭代器与列表比较,迭代器是惰性计算的,,只有在需要返回下一个数据时才计算,更节省内存 
缺点: 
     1):无法获取迭代器的长度,使用不如列表索引取值灵活 
   2):一次性的,只能往后取值,不能倒着取值

如何查看一个对象是不是可迭代对象和迭代器对象

from collections import Iterable,Iterator#导入模块
list = [‘a‘,‘b‘,1,2]
list_iter = list.__iter__()
#isinstance
print(isinstance(list,Iterable))#是否是可迭代对象
print(isinstance(list,Iterator))#是否是迭代器对象
print(isinstance(list_iter,Iterable))
print(isinstance(list_iter,Iterator))

小结:

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列,也就是说你需要取值时才给你取;

集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

 

协程函数

如果在一个函数内部yield的使用方式是表达式形式的话,如x=yield,那么该函数称为协程函数,比线程更小

def eater(name):
    food_list=[]
    while True:
        food=yield food_list
        food_list.append(food)#因为有yied

e=eater(‘铁根‘)   #一个生成器的内存地址
print(next(e))   #初始化迭代器
print(e.send(‘包子‘))
print(e.send(‘韭菜馅包子‘))
print(e.send(‘大蒜包子‘)) 

2.生成器

通过列表生成式,我们可以创建一个列表。但是我们知道受内存的限制,我们的列表容量是有限的,而且创建包含100万个元素的列表,我们只用到前几个,剩下的都是浪费内存空间。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

l=[x*x for x in range(10)]
print(l)

g=(x*x for x in range(10))
print(g)


打印结果
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x0000000001E70F10>

 取generator值用__next__()一个一个打印出来

print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())

0
1
4
9
16
25

generator也是可迭代对象,所以也可以使用for循环

for i in g:
    print(i)


0
1
4
9
16
25
36
49
64
81

所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return ‘done‘
fib(20)
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765

  

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return ‘done‘

f = fib(6)
print(f)
<generator object fib at 0x0000000002170F10>

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回再次执行时从上次返回的yield语句处继续执行。

定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator

我们把函数变成generator后,基本上也不再使用__next__()方法,都是用for循环来迭代,但是用for循环调用generator时,发现拿不到generator的return语句的返回值。

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        # print(b)
        yield b
        a, b = b, a + b
        n = n + 1
    return ‘done‘
for i in fib(5):
    print(i)

1
1
2
3
5

如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中:

while True:
    try:
        x=next(g)
        print(x)
    except StopIteration as e:
        print(‘Generator return value:‘,e.value)
        break



2
3
5
Generator return value: done

看到一张图方便理解列表等容器,生成器,可迭代对象,迭代器之间的关系

技术分享

 

 

小结:

generator可以是表达式也可以是函数,它保存的是算法,同时它是iterable对象,它也是一个generator。

技术分享

python迭代器和生成器