Python生成器淺談
首先,生成器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