day4-生成器
列表生成式
定义
列表[0,1,2,3,4,5,6,7,8,9],需求是把列表中的每个元素乘以2,你是怎么实现的呢?
>>> [i*2 for i in range(10)] [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
上面的列表相当于如下代码生成的
a = [] for i in range(10): a.append(i*2) print(a) #输出 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
产生背景
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环以便计算的机制,称为生成器:generator
用法:要创建一个generator,有很多种方式。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator
1.生成器的创建
# 每循环一次乘以2 >>> a = (i*2 for i in range(10)) #通过()产生一个生成器并把它赋给a,不需要开始把所有读到内存 >>> a <generator object <genexpr> at 0x1054b02b0> >>> a = (i+3 for i in range(10000)) >>> for i in a: # 生成器只有在调用时才会生成相应的数据 print(i) 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Traceback (most recent call last): File "<pyshell#13>", line 2, in <module> print(i) File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/idlelib/PyShell.py", line 1344, in write return self.shell.write(s, self.tags) KeyboardInterrupt
生成器不支持像列表一样的切片和取值功能,因为它还没有生成和读到内存
>>> a[5] Traceback (most recent call last): File "<pyshell#9>", line 1, in <module> a[5] TypeError: 'generator' object is not subscriptable
如果需要访问生成器n中的值,python2是通过next()方法去获得generator的下一个返回值,python3是通过__next__()去获得generator的下一个返回值:
# 只有一个__next__()方法
>>> a.__next__() 20 >>> a.__next__() 21 >>> a.__next__() 22 >>> a.__next__() 23 >>> a.__next__() #只记录当前位置 24
总结:
1.generator保存的是算法,每次调用next方法时,就会计算下一个元素的值,直到计算到最后一个元素,如果没有更多元素,则会抛出StopIteration的错误。
2.generator只记住当前位置,它访问不到当前位置元素之前和之后的元素,之前的数据都没有了,只能往后访问元素,不能访问元素之前的元素。
generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以使用
函数来实现
比如,著名的斐波拉契数列(Fibonacci),除第一个数和第二个数外,任意一个数都可由前两个数相加得到:
1,1,2,3,5,8,13,21,34,......
斐波那契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
def fib(max): n,a,b = 0,0,1 # 每循环一次就把a往后移动一次 while n < max: print(b) a,b=b,a+b n=n+1 return"done" fib(10) #输出 1 1 2 3 5 8 13 21 34 55
done
这边需要注意的是赋值语句:
a,b = b,a+b
相当于:
t =( b,a+b) # t是一个tuple a=t[0] b=t[1]
不必显式写出临时变量t就可以赋值
接着我们现在要将此函数变为一个生成器?
答:只需把print(b)变为yield b
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" print(fib(10)) #输出 <generator object fib at 0x102980888>
再使用__next__()方法访问生成器中的元素:
def fib(max): n,a,b=0,0,1 while n<max: yield b #把b返回到外面,所以就在这使用yield b(yield是保存了函数的中断状态) a,b=b,a+b n=n+1 return"done" x=(fib(10)) print(x.__next__()) print(x.__next__()) print(x.__next__()) print("===干点别的事=====") print(x.__next__()) # next是唤醒yield print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) #输出 1 1 2 ===干点别的事===== 3 5 8 13 21 34 55 Traceback (most recent call last): File "/Users/huwei/PycharmProjects/s14/module_2/斐波那契.py", line 21, in <module> print(x.__next__()) StopIteration: done
当打印的次数多过循环的次数时,抛出异常,并输出return 后面的语句,我们可以通过异常处理进行处理,使程序运行不会报错,那么
x=(fib(10)) while True: try: print(x.__next__()) print(x.__next__()) print(x.__next__()) print("===干点别的事=====") print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) except StopIteration as e: print("生成器返回值:",e.value) break #输出 1 1 2 ===干点别的事===== 3 5 8 13 21 34 55 生成器返回值:done
到现在已经不称作函数了,现在已经是一个生成器,所以在异常的时候,rerurn是返回的是打印异常的消息。
我们也可以使用for循环进行打印,超过循环最大次数,不会出现异常或报错:
x=(fib(10)) print(x.__next__()) print(x.__next__()) print(x.__next__()) print("===干点别的事=====") print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) print(x.__next__()) print("===开始循环=====") For i in x: print(i) #输出 1 1 2 ===干点别的事===== 3 5 8 13 21 ===开始循环===== 34 55
上面的列子都是从上往下执行,下面必须等待上面执行完,下面程序才能执行,对于这种单线程,只能这样