对于python生成器的理解

  • baagee 发布于 2017-07-26 10:59:46
  • 分类:Python
  • 807 人围观
  • 0 人喜欢

1. 什么是生成器

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

2. 创建生成器方法1

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

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

In [11]: next(b)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-11-641a931447e8> in <module>()
----> 1 next(b)

StopIteration:

生成器保存的是算法,每次调用 next(b) ,就计算出 b 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。当然,这种不断调用 next() 实在是太变态了,正确的方法是使用 for 循环,因为生成器也是可迭代对象。所以,我们创建了一个生成器后,基本上永远不会调用 next() ,而是通过 for 循环来迭代它,并且不需要关心 StopIteration 异常。

In [3]: for x in b:
   ...:     print(x)
   ...:     
0
1
2
3
4
5
6
7
8

3. 创建生成器方法2

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:1, 1, 2, 3, 5, 8, 13, 21, 34, ...

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

In [4]: def fib(end):
   ...:     n=0
   ...:     a,b=0,1
   ...:     while n<end:
   ...:         print(b)
   ...:         a,b=b,a+b
   ...:         n+=1
   ...:         

In [5]: fib(9)
1
1
2
3
5
8
13
21
34

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。但是上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

In [6]: def fib(end):
    n=0
    a,b=0,1
    while n<end:
        yield b
        a,b=b,a+b
        n+=1 
   ...:         

In [7]: f=fib(9)

In [8]: f
Out[8]: <generator object fib at 0xb672a41c>

In [9]: f.__next__()
Out[9]: 1

In [10]: f.__next__()
Out[10]: 1

In [11]: f.__next__()
Out[11]: 2

In [12]: next(f)
Out[12]: 3

In [13]: next(f)
Out[13]: 5

In [14]: for x in f:
   ....:     print(x)
   ....:     
8
13
21
34

可以看出f.__next__()和next(f)是一样的。通过for循环可以遍历生成器,并且不会报错。

4. 含有yield的生成器程序是怎么运行的呢?

先来看以下代码:

def fib():
    print('start')
    a, b = 0, 1
    for i in range(9):
        print('---1---')
        yield b
        print('-----2-----')
        a, b = b, a + b
        print('------3----')
    print('end')

a = fib()
print(a)
print(next(a))
print('####################')
print(next(a))
print('####################')
print(next(a))

运行结果是:

<generator object fib at 0xb70b398c>
start
---1---
1
####################
-----2-----
------3----
---1---
1
####################
-----2-----
------3----
---1---
2
[Finished in 0.1s]

通过结果看出第一次next时运行到yield b然后把b返回了,然后程序就停止运行了,第二次调用next时接着上次运行的地方开始运行,再次循环到yield b时停止,以此类推...

5.  send的使用

send方法是啥?干嘛的?先看以下程序,用代码说话;

def test():
    i = 0

    while i < 5:
        temp = yield i
        print(temp)
        i += 1

t = test()
print(next(t))
print('#########')
print(t.send(None))
print('############')
print(t.send('---=-=-=-='))
print('#############')
print(next(t))
print('###############')
print(t.send('hahhaha'))

结果:

0
#########
None
1
############
---=-=-=-=
2
#############
None
3
###############
hahhaha
4
[Finished in 0.1s]

当程序运行到temp=yield i时,先算等号右边的,然后就停住了,所以只打印了0。

第二次使用send()方法,传递了None,把这个参数给了temp,所以打印出了None和1。第三次send('---=-=-=-='),所以会打印“

---=-=-=-=

2

当不使用send方法传参数时,temp就是None了。

6. 生成器的总结

生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制中的位置。

生成器的特点:

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


转载请说明出处:baagee博客 » 对于python生成器的理解
标签: Python 生成器

评论

点击图片切换
还没有评论,快来抢沙发吧!