python生成器:generator

1.什么是生成器generator:

通过列表生成式(列表推导式),我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

创建生成方法一:

创建 a 和 b 的区别仅在于最外层的 [ ] 和 ( ) , a 是一个列表,而 b 是一个生成器。我们可以直接打印出a的每一个元素,但我们怎么打印出b的每一个元素呢?如果要一个一个打印出来,可以通过 next() 函数获得生成器的下一个返回值:

>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> type(a)
<class 'list'>
>>> b = (x*3 for x in rang(10))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'rang' is not defined
>>> b = (x*3 for x in range(10))
>>> type(b)
<class 'generator'>
>>> next(b)
0
>>> next(b)
3
>>> next(b)
6
>>> next(b)
9
...
...
...
>>> next(b)
27
>>> next(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

也可以通过for循环进行遍历

for n in b:
    print(n)

0
3
6
9

生成器保存的是算法,每次调用next(b),就计算出b的下一个元素的值,直到计算到最后一个元素,没有更多的元素的时候会抛出StopIteration的异常。实际操作中很少有人使用这种方法,更多的是使用for循环的方法,因为生成器也是可迭代对象,

创建生成器方法二:在函数中使用yield变成生成器

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

函数生成著名的斐波拉契数列(除第一个和第二个数外,任意一个数都可以通过前两个数相加得到)

1, 1, 2, 3, 5, 8, 13, 21, 34, 55,89,144,233...

斐波拉契数列用列表推导式很难写出来,但是用函数把它打印出来却很容易(下列代码中,b锁对应的就是斐波拉契数列)

>>> a,b = 0,1
>>> b
1
>>> a,b=b,a+b
>>> b
2
>>> a,b=b,a+b
>>> b
3
>>> a,b=b,a+b
>>> b
5
>>> a,b=b,a+b
>>> b
8

如下所示的代码中,其实fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始推算出后面任意的元素,这种逻辑非常类似雨generator

>>> def fib(length):
...     a,b = 0,1
...     n = 0
...     while n < length:
...         print(b)
...         a,b=b,a+b
...         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
'Done'
>>> 

使用yield变成生成器
yield是一个关键词,如同return不同之处在于yield返回的是一个生成器,也就是说上面的函数和生成器generator仅仅一步之遥,要把yield改变成genertor,只需要把println(b)改写成yield b 就行了。

>>> def fib(length):
...     a,b = 0,1
...     n = 0
...     while n < length:
...         yield b
...         a,b=b,a+b
...         n += 1
...     return "Done"
...
...
...f = fib(20) //变成生成器只能这么调用。
...next(f)
...next(f)
...next(f)
...next(f)
...next(f)

在python3中我们可以引入模块然后调用

from test import *
>>> b = fib(20)
>>> next(b)
1
>>> next(b)
1
>>> next(b)
2
>>> next(b)
3
>>> next(b)
5
>>> next(b)
8

捕获StopIteration错误得到返回值
在上面的代码中我们不断的调用yield,就会不断的获取下一个元素,当最后一个元素被获取后会报错,这时候就需要我们进行错误的捕获。
用for循环调用generator式,发现拿不到generator的return语句的返回值,如果想要拿到返回值,必须捕获stopiterator错误,返回值包含在stopiterator的value中:

f = fib(5)
try:
    while True:
        next(f)
    except Exception as result:
        print(result)
1
1
2
3
5
Done

常用方法

1.使用send
next(f)等价f.send(None)
执行到yield时,gen函数作用暂时保存,返回i的值;temp接收下次f.send(“python”),send发送过来的值,next(f)等价f.send(None)

>>> def gen():
...     i = 0
...     while i < 5:
...         temp = yield i
...         print("temp=",temp)
...         i += 1
... 
>>> f  = gen()
>>> print(f.send(None))
0
>>> print(f.send("no1"))
temp= no1
1
>>> print(f.send("no2"))
temp= no2
2
>>> print(f.send("no3"))
temp= no3
3
>>> print(f.send("no4"))
temp= no4
4

send()的作用
使用send的时候应该注意不能一开始就使用
使用send的时机

使用next函数
使用_ next _()方法-扩展

>>> def gun():
...     i = 0
...     while i < 5:
...         temp = yield i
...         print("temp=",temp)
...         i += 1
... 
>>> f = gun()
>>> print(f.__next__())
0
>>> print(f.__next__())
temp= None
1
>>> print(f.__next__())
temp= None
2
>>> print(f.__next__())
temp= None
3
>>> print(f.__next__())
temp= None
4

yield扩展-多任务执行

>>> def test1():
...     while True:
...         print("-------------test1-------------")
...         yield None
... 
>>> def test2():
...     while True:
...         print("-------------test2-------------")
...         yield None
... 
>>> t1 = test1()
>>> t2 = test2()
>>> while True:
...     t1.__next__()
...     t2.__next__()
... 
-------------test1-------------
-------------test2-------------
-------------test1-------------
-------------test2-------------
-------------test1-------------
-------------test2-------------
-------------test1-------------
-------------test2-------------

总结:
生成器是一个函数,它记住上一次返回时在函数体中的位置,对生成器函数的第二次(或第n次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
生成器不仅“记住“了它数据状态,生成器还“记住“了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。

生成器的特点:
1.节约内存
2.迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的。

猜你喜欢

转载自blog.csdn.net/Hash_Map_zong/article/details/82182816