生成器
上一节列表生成式可以用来生成一个完整list,但是如果需要的list容量很大呢?如果需要一个100万个元素的列表,难道要生成这样一个list么,那不是很占内存么?更何况我们可能并不需要这个列表中的所有元素。
是不是没有必要完整生成一个list,而是把规律和算法写入,让其自动推算所有元素呢?Python提供了这样一个工具:生成器generator,通过一边循环一边计算:
一个简单的生成器创建方法就是把列表生成式的[]变成():
>>> L=[x*x for x in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> g=(x*x for x in range(10)) >>> g <generator object <genexpr> at 0x112fd8f20> >>> next(g) 0 >>> next(g) 1 >>> next(g) 4 >>> next(g) 9 >>> next(g) 16 >>> next(g) 25 >>> next(g) 36 >>> next(g) 49 >>> next(g) 64 >>> next(g) 81 >>> next(g) Traceback (most recent call last): File "<pyshell#38>", line 1, in <module> next(g) StopIteration >>>
L和g的区别仅仅在于创建时一个用的[]一个用的(),L是一个list,g是一个生成器,我们直接输入L就输出了整个list的元素,上面的例子中,我们使用next函数把生成器中每个元素一个个输出出来,当超出生成器的范围时,就直接报错StopIteration(停止迭代)。
生成器保存的是算法,像上面用next一个一行的输出太繁琐,我们可以使用for循环的方式迭代输出:
>>> f=(x*x for x in range(10)) >>> for n in f: print(n) 0 1 4 9 16 25 36 49 64 81
对于复杂的生成器,for循环无法表达时,可以通过函数实现,例如斐波拉契数列(Fibonacci),头两个数是1和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' >>> fib(7) 1 1 2 3 5 8 13 'done'
函数fib其实就是定义了裴波拉切数列的推算方式,这和生成器是类似的,那么如何把这个函数变成一个生成器呢?只需要把print(b)变成yield b就可以:
>>> def fib(max): n,a,b=0,0,1 while n<max: yield b a,b=b,a+b n=n+1 return 'done' >>> f=fib(6) >>> f <generator object fib at 0x11309f120> >>> next(f) 1 >>> next(f) 1 >>> next(f) 2 >>> next(f) 3 >>> next(f) 5 >>> next(f) 8 >>> next(f) Traceback (most recent call last): File "<pyshell#86>", line 1, in <module> next(f) StopIteration: done
这样fib就变成一个生成器了,函数执行是顺序执行,遇到return或者执行到底了就返回,而在生成器中,每次next都是遇到yield就返回,下次next从上次yield的位置继续执行。我们定义一个生成器依次输出1、3、5:
>>> def odd(): print('step 1') yield 1 print('step 2') yield 3 print('step 3') yield 5 >>> o=odd() >>> next(o) step 1 1 >>> next(o) step 2 3 >>> next(o) step 3 5 >>> next(o) Traceback (most recent call last): File "<pyshell#99>", line 1, in <module> next(o) StopIteration
上面的例子可以看到,调用生成器首先要有一个对象o,用next挨个输出时,遇到yield就中断,再次next就接着上次的位置继续,直到最后一次没有可执行的位置了,就直接报错了。
这里next只是为了展示运算的过程,在实际使用中我们依然使用for循环来迭代,接着上面的fib生成器:
>>> for n in fib(7): print(n) 1 1 2 3 5 8 13 >>>
我们看到用for迭代生成器fib时,并没有输出最后返回值’done’,要想拿到返回值就需要捕捉最后一次迭代执行时,终止迭代的报错信息StopIteration,’done’这个返回值就包含在这里:
>>> g=fib(7) >>> while True: try: x=next(g) print('g:',x) except StopIteration as e: print(e.value) break g: 1 g: 1 g: 2 g: 3 g: 5 g: 8 g: 13 done