[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)]

留言

這個網誌中的熱門文章

[Linux] Linux下查詢硬體記憶體資訊 Memory Information

[Other] Chrome 重新整理所有開啟頁面

[Python] Simple Socket Server