[Python] easy generator
python的generator一直都相當好用
這邊參考了官網跟Expert Python Programming
官網: http://docs.python.org/2/tutorial/classes.html#generators
首先進入generator之前, 要先了解一下iterator
使用iterator很簡單, 只要下iter即可
i = iter('apple')
之後可以用i.next()來呼叫每個元素, 就會一個一個印出a,p,p,l,e
但是要是已經呼叫到e, 再呼叫next()就會出現error
Traceback (most recent call last): File "", line 1, in StopIteration
所以要用iterator建議是做成class來使用, 並且設定一些exception狀況
那, generator則是用來產生iterator的強力工具, 配合著yields使用
我用EPP(Expert Python Programming)的Fibonacci series範例
def fib(): a, b = 0, 1 while True: yield b a, b = b, a + b
這樣就是一個漂亮的費波那西數列"產生器"
使用方式
i = fib()
接下來只要用next()就可以一直獲取下一個費波那西數
>>> i.next() 1 >>> i.next() 1 >>> i.next() 2 >>> i.next() 3 >>> i.next() 5 >>> i.next() 8 >>> i.next() 13 >>> i.next() 21 >>> i.next() 34 >>> i.next() 55 >>> i.next() 89 >>> i.next() 144
fib()就變成了一個generator function, 他可以generator fibonacci series
yield是很好用的, 也有幾個不錯的中文網站介紹
caterpillar也有介紹過: http://caterpillar.onlyfun.net/Gossip/Python/YieldGenerator.html
那不熟悉yield的人可以這樣思考, 每次我call fib()一次, 他進入while true之後
碰到yield b, 就會return b出去, 而且, 會紀錄當下fib()執行的參數數值, 等下一次用
也就是第一次呼叫next()的時候, a = 0, b = 1, 回傳1, 並且停在yield的地方(a,b值會記錄下來)
等到再呼叫next()的時候, 就會從yield之後繼續執行, 那因為是while True, 所以下次又會停在yield b
就是靠著yield來配合產生出一連串的行為
那再複雜一點點, 可以透過send來跟yield溝通
聽起來很複雜, 其實就是在function裡面下yield的時候, 改成
s = yield b
這要怎麼利用呢? 其實本來是i.next(), 改成i.send(50)
那就會再度進入generator function, 而且會把50傳給s, 至於要怎麼用就看程式設計了
所以send跟yield差不多, 但是多了一個傳參數的功能, 可是卻可能大大的改變了原本的generator的設計流程
算是一個相當好用的方法
那generator再很多地方都有出現過
像是常見的list comprehensive
ex:
[x**2 for x in range(10)]
這結果是
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]但是其實我們也可以把這樣的東西變成generator, 可是用yield x**2 這樣太麻煩了
有更方便的作法, 把[]改成()即可
i = (x**2 for x in range(10))
接下來只要跟平常使用i.next()去呼叫即可, 這種方式稱呼為generator expression or genexp
EPP這本書有多介紹了三個滿方便的iterator tool
1. islice, 用來切割字串相當方便, 範例我用python官網的
from itertools import islice islice('ABCDEFG', 2) # A B, 也就是切割前面兩個字元出來 islice('ABCDEFG', 2, 4) # C D, 從第二個切割到第四個 islice('ABCDEFG', 2, None) # C D E F G, 從第二個往後切到底 islice('ABCDEFG', 0, None, 2) # A C E G, 隔一個切(default是 "0, None, 1" )
那使用跟上述的iterator一樣, 用變數接取之後, 用next()來呼叫每次傳出結果
2. tee, 有沒有發覺iterator一跑, 我要恢復之前的就很麻煩?, 可以先用tee做出多分的iterator來使用
from itertools import tee i = islice('ABCDEFG', 2) a, b = tee(i)
你就有兩個a, b的iterator可以使用, 而且彼此不相關, 不會互相影響, 如果想要多個iterator可以改成
a, b, c = tee(i, 3)
3. groupby, group by python官網的範例稍微有點不明確, 我用EPP的範例當例子
首先先提一下RLE
Run Length Encoding(RLE), 很常見的一種壓縮技巧, 簡單講, appllleeee, 可以變成 a1p2l3e4
就是把重複的字元合併起來, 這邊會用此範例說明, 我稍微修改一點書上的code
from itertools import groupby words = 'appllleeee' i = groupby(words)
照前面的tool看起來都要這樣用, 不過這樣是不夠的
如果用i.next(), 會出現類似('a', <itertools ._grouper="" 0xb6a93f4c="" at="" object="">)的結果
看出來有兩個回傳, 所以改成用
i1, i2 = i.next()
會成功看到i1是字元, 但是i2卻怪怪的, 出現的是<itertools._grouper object at 0xb6a93f8c>
其實要轉型一下, 用list(i2), 就可以看到重複的字元, 再加上len就可以算出次數
所以如果要顯示類似RLE的結果其實可以一行解決
from itertools import groupby words = 'appllleeee' print [(i, len(list(j))) for i, j in groupby(words)]
就可以看到
[('a', 1), ('p', 2), ('l', 3), ('e', 4)]
留言
張貼留言