1. 程式人生 > >Python生成器淺談

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