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