day4-生成器

一、概述

  我们在使用一组数据时,通常情况下会定义一个列表,然后循环里面的元素,但是你想过没有,如果你只需要使用列表中的1-2个元素,其他的元素用不到,这样就会造成资源的浪费,这样不能很好的合理的利用我们机器的资源,那我们如何合理高效的利用这些利用这些资源,并且提高我们程序的运行速度呢?下面我们就来讲讲我们今天最关键的知识点,生成器。

二、列表生成式

1、定义

看列表[0,1,2,3,4,5,6,7,8,9],需求是把列表中的每个元素加1,你是怎么实现的呐?

a = [0,1,2,3,4,5,6,7,8,9]
for index,i in enumerate(a):
    a[index] += 1
print(a)

#输出
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

 当然可能还有其他方法,这边就逐一介绍了,我这边有一个最简单的方法:

>>> [ i*2 for i in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

 以上这种就叫列表生成

三、生成器

  正如我之前所说的,我们可以通过列表生成式,直接去创建一个列表。但是收到内存的限制,列表的容量是有限的。如果我们在创建一个包含100万个元素的列表,甚至更多,不仅占用了大量的内存空间,而且如果我们仅仅需要访问前面几个元素时,那后面很大一部分的占用的空间都白白浪费掉了。这个并不是我们所希望看到的。

  所以我们就诞生了一个新的名词叫生成器:generator。下面我们就来说说这个生成器的作用。

  生成器的作用:列表的元素按某种算法推算出来,我们在后续的循环中不断推算出后续的元素,在python中,这种一边循环一边计算的机制,称之为生成器(generator)。

1、创建生成器

>>> m=[i*2 for i in range(10)]
>>> m
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]  #生成一个list
>>> n = (i*2 for i in range(10))
>>> n
<generator object <genexpr> at 0x00000000033A4FC0> #生成一个generator

 如果需要访问生成器n中的值,python2是通过next()方法去获得generator的下一个返回值,python3是通过__next__()去获得generator的下一个返回值:

#python 3的访问方式用__next__()
>>> n.__next__() 
0
>>> n.__next__()
2
>>> n.__next__()
4
>>> n.__next__()
6
>>> n.__next__()
8
>>> n.__next__()  #没有元素时,则会抛出抛出StopIteration的错误
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    n.__next__()
StopIteration


#python2的访问方式用next()
>>> n.next()  #可以用n.next()
0
>>> next(n)  #也可以用next(n)  
2
>>> n.next()
4
>>> n.next()
6
>>> n.next()
8
>>> n.next()  #没有元素时,则会抛出抛出StopIteration的错误

Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    n.next()
StopIteration

 小结:①generator保存的是算法,每次调用next方法时,就会计算下一个元素的值,直到计算到最后一个元素,如果没有更多元素,则会抛出StopIteration的错误。

    ②generator只记住当前位置,它访问不到当前位置元素之前和之后的元素,之前的数据都没有了,只能往后访问元素,不能访问元素之前的元素。

2、用for循环去访问generator中的元素

用next方法去一个一个访问,仿佛有点变态,也不切实际,正确的方法是使用for循环去访问,因为generator也是可迭代对象,代码如下:

>>> res = (i*2  for i in range(3))  #创建一个生成器
>>> res
<generator object <genexpr> at 0x0000000003155C50>
>>> for i in res:    #迭代生成器中的元素
    print(i)

#输出    
0
2
4

 所以我们在创建一个生成器以后,基本不会用next方法去访问,而是通过for循环来迭代它,并且更不用关心StopIteration错误。

 三、函数实现生成器

上面推算比较简单,但是推算的算法比较复杂,用类似列表生成式的for循环无法实现,那怎么办呢?比如下面一个例子,用列表生成式无法实现。

1、斐波那契数列

实现原理:除第一个和第二个数外,任意一个数都可由前两个数相加得到: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---"

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

这边需要注意的是赋值语句:

a , b = b ,a+b

 相当于:

t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

 上面的执行结果:

fib(5)
#执行结果
1
1
2
3
5
----done---

 根据这种逻辑推算非常类似一个生成器(generator)。但是怎么把一个函数转换成一个生成器呢?

2、用yield函数转换为生成器(generator)

def fib(max):
    n,a,b = 0,0,1
    while n < max:
        yield b       #用yield替换print,把fib函数转化成一个生成器
        a , b = b ,a+b
        n = n+1

    return  "----done---"

 以上就是生成器(generator)另外一种定义方法。如果一个函数中包含yield关键字,那么这个函数就不是一个普通的函数,而是一个生成器(generator)。

f = fib(5)
print(f)

#输出
<generator object fib at 0x0000000000D1B4C0>

 这边有两个难理解地方:①函数是顺序执行的,遇到return语句或者最后一行函数语句就返回

            ②变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

①访问元素:

f = fib(5)
print(f.__next__())
print(f.__next__())
print(f.__next__())
print("我在干别的事情")
print(f.__next__())
print(f.__next__())#访问的是最后一个元素
print(f.__next__()) #没有多余的元素

#输出
1
1
2
-----我在干别的事情-----
3
5
Traceback (most recent call last):
  File "D:/PycharmProjects/pyhomework/day4/生成器/fib.py", line 20, in <module>
    print(f.__next__())
StopIteration: ----done---

 从上面的例子可以看出来:①我访问生成器中的元素,不用是连续的,我可以中间去执行其他程序,向想什么时候执行,可以再回头去执行。

             ②return在这边作用就是当发生异常时,会打印ruturn后面的值。

②for循环访问

f = fib(5)
for i in f:
    print(i)

#输出
1
1
2
3
5

 ③捕获这个StopIteration这个异常

f = fib(5)
while True:
    try:
        x = f.__next__()
        print("f:",x)
    except StopIteration as e:  #当try中的程序执行错误了,才会执行except下面的代码
        print("Generator return value:",e.value)
        break

#执行结果
f: 1
f: 1
f: 2
f: 3
f: 5
Generator return value: ----done---

 关于这个异常处理,后续会继续发博客更新,请大家敬请期待。。。。。

 

posted @ 2017-03-22 12:42  帅丶高高  阅读(261)  评论(0编辑  收藏  举报