首先,生成器generator是一个对象,而且是可迭代的,它保存的是算法,只有在用的时候调用它它才会去计算,这样就节省了大量的空间。
创建generator有很多方法,比较简单的一种就是把列表生成式的[]换成(),即可生成一个生成器。
>>> [x * x for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x00000069C5C79DB0>
可以看到g此时已经变为生成器对象,不会直接返回结果,需要调用时才会去返回,一次一次的调用可以使用next()
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "<pyshell#21>", line 1, in <module>
next(g)
StopIteration
当所有值都取完后,就会报错StopIteration,抛出异常,可见生成器在没有元素可取时就会报错,只能用的时候才去调用。
当然这是笨方法,因为生成器是可迭代的对象,所以正确的打开方式应该是for循环
>>> a = (x * x for x in range(5))
>>> for i in a:
print(i)
0
1
4
9
16
>>> for i in a:
print(i)
>>>
再次去执行for循环,没有打印出结果,因为此时已经没有更多的元素可以打印,但是不会抛出异常,所以使用for循环不用考虑异常的问题。
另外,生成器还可以由函数改成,在需要返回数据的地方加上yield即可,如下,把斐波那契函数改成生成器。
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a , b = b, a + b n += 1 return 'done'
此时,fib()函数就需要循环调用才会显示每次的结果
g = fib(9),做完赋值操作,此时不会像函数一样调用就会马上去执行函数体,而且需要去调用g一次才会去执行一次
可以使用next(g)去调用,也可以使用for循环,但是因为return里的内容保存在StopIteration的value中,但是for循环不会抛出异常,所以使用for循环拿不到return的返回值,需要打印异常里的value值才会拿到return的返回值。
for i in g: print(i)
1
1
2
3
5
8
13
21
34
拿到return返回值的方法,需要使用next一次次调用,抛出异常后捕获到再打印出来:
while True: try: x = next(g) print(x) except StopIteration as e: print("generator return value:", e.value) break
运行结果:
1
1
2
3
5
8
13
21
34
generator return value: done