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---
关于这个异常处理,后续会继续发博客更新,请大家敬请期待。。。。。