在 Python 文檔中,實(shí)現(xiàn)接口通常被稱為遵守協(xié)議。因?yàn)?"弱類型" 和 "Duck Type" 的緣故,很多靜態(tài)語言中繁復(fù)的模式被悄悄抹平。
迭代器協(xié)議,僅需要 iter() 和 next() 兩個(gè)方法。前者返回迭代器對(duì)象,后者依次返回?cái)?shù)據(jù),直到引發(fā) StopIteration 異常結(jié)束。
最簡單的做法是用內(nèi)置函數(shù) iter(),它返回常用類型的迭代器包裝對(duì)象。問題是,序列類型已經(jīng)可以被 for 處理,為何還要這么做?
>>> class Data(object):
... def __init__(self):
... self._data = []
...
... def add(self, x):
... self._data.append(x)
...
... def data(self):
... return iter(self._data)
>>> d = Data()
>>> d.add(1)
>>> d.add(2)
>>> d.add(3)
>>> for x in d.data(): print x
1
2
3
返回迭代器對(duì)象代替 self._data 列表,可避免對(duì)象狀態(tài)被外部修改?;蛟S你會(huì)嘗試返回 tuple,但這需要復(fù)制整個(gè)列表,浪費(fèi)更多的內(nèi)存。
iter() 很方便,但無法讓迭代中途停止,這需要自己動(dòng)手實(shí)現(xiàn)迭代器對(duì)象。在設(shè)計(jì)原則上,通常會(huì)將迭代器從數(shù)據(jù)對(duì)象中分離出去。因?yàn)榈餍枰S持狀態(tài),且可能有多個(gè)迭代器在同時(shí)操控?cái)?shù)據(jù),這些不該成為數(shù)據(jù)對(duì)象的負(fù)擔(dān),無端提升了復(fù)雜度。
>>> class Data(object):
... def __init__(self, *args):
... self._data = list(args)
...
... def __iter__(self):
... return DataIter(self)
>>> class DataIter(object):
... def __init__(self, data):
... self._index = 0
... self._data = data._data
...
... def next(self):
... if self._index >= len(self._data): raise StopIteration()
... d = self._data[self._index]
... self._index += 1
... return d
>>> d = Data(1, 2, 3)
>>> for x in d: print x
1
2
3
Data 僅僅是數(shù)據(jù)容器,只需 iter 返回迭代器對(duì)象,而由 DataIter 提供 next 方法。
除了 for 循環(huán),迭代器也可以直接用 next() 操控。
>>> d = Data(1, 2, 3)
>>> it = iter(d)
>>> it
<__main__.DataIter object at 0x10dafe850>
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
StopIteration
基于索引實(shí)現(xiàn)的迭代器有些丑陋,更合理的做法是用 yield 返回實(shí)現(xiàn)了迭代器協(xié)議的 Generator 對(duì)象。
>>> class Data(object):
... def __init__(self, *args):
... self._data = list(args)
...
... def __iter__(self):
... for x in self._data:
... yield x
>>> d = Data(1, 2, 3)
>>> for x in d: print x
1
2
3
編譯器魔法會(huì)將包含 yield 的方法 (或函數(shù)) 重新打包,使其返回 Generator 對(duì)象。這樣一來,就無須廢力氣維護(hù)額外的迭代器類型了。
>>> d.__iter__()
<generator object __iter__ at 0x10db01280>
>>> iter(d).next()
1
協(xié)程
yield 為何能實(shí)現(xiàn)這樣的魔法?這涉及到協(xié)程 (coroutine) 的工作原理。先看下面的例子。
>>> def coroutine():
... print "coroutine start..."
... result = None
... while True:
... s = yield result
... result = s.split(",")
>>> c = coroutine() # 函數(shù)返回協(xié)程對(duì)象。
>>> c.send(None) # 使用 send(None) 或 next() 啟動(dòng)協(xié)程。
coroutine start...
>>> c.send("a,b") # 向協(xié)程發(fā)送消息,使其恢復(fù)執(zhí)行。
['a', 'b']
>>> c.send("c,d")
['c', 'd']
>>> c.close() # 關(guān)閉協(xié)程,使其退出?;蛴?c.throw() 使其引發(fā)異常。
>>> c.send("e,f") # 無法向已關(guān)閉的協(xié)程發(fā)送消息。
StopIteration
協(xié)程執(zhí)行流程:
close() 引發(fā)協(xié)程 GeneratorExit 異常,使其正常退出。而 throw() 可以引發(fā)任何類型的異常,這需要在協(xié)程內(nèi)部捕獲。
雖然生成器 yield 能輕松實(shí)現(xiàn)協(xié)程機(jī)制,但離真正意義上的高并發(fā)還有不小的距離。可以考慮使用成熟的第三方庫,比如 gevent/eventlet,或直接用 greenlet。
善用迭代器,總會(huì)有意外的驚喜。
生產(chǎn)消費(fèi)模型
利用 yield 協(xié)程特性,我們無需多線程就可以編寫生產(chǎn)消費(fèi)模型。
>>> def consumer():
... while True:
... d = yield
... if not d: break
... print "consumer:", d
>>> c = consumer() # 創(chuàng)建消費(fèi)者
>>> c.send(None) # 啟動(dòng)消費(fèi)者
>>> c.send(1) # 生產(chǎn)數(shù)據(jù),并提交給消費(fèi)者。
consumer: 1
>>> c.send(2)
consumer: 2
>>> c.send(3)
consumer: 3
>>> c.send(None) # 生產(chǎn)結(jié)束,通知消費(fèi)者結(jié)束。
StopIteration
改進(jìn)回調(diào)
回調(diào)函數(shù)是實(shí)現(xiàn)異步操作的常用手法,只不過代碼規(guī)模一大,看上去就不那么舒服了。好好的邏輯被切分到兩個(gè)函數(shù)里,維護(hù)也是個(gè)問題。有了 yield,完全可以用 blocking style 編寫異步調(diào)用。
下面是 callback 版本的示例,其中 Framework 調(diào)用 logic,在完成某些操作或者接收到信號(hào)后,用 callback 返回異步結(jié)果。
>>> def framework(logic, callback):
... s = logic()
... print "[FX] logic: ", s
... print "[FX] do something..."
... callback("async:" + s)
>>> def logic():
... s = "mylogic"
... return s
>>> def callback(s):
... print s
>>> framework(logic, callback)
[FX] logic: mylogic
[FX] do something...
async:mylogic
看看用 yield 改進(jìn)的 blocking style 版本。
>>> def framework(logic):
... try:
... it = logic()
... s = next(it)
... print "[FX] logic: ", s
... print "[FX] do something"
... it.send("async:" + s)
... except StopIteration:
... pass
>>> def logic():
... s = "mylogic"
... r = yield s
... print r
>>> framework(logic)
[FX] logic: mylogic
[FX] do something
async:mylogic
盡管 framework 變得復(fù)雜了一些,但卻保持了 logic 的完整性。blocking style 樣式的編碼給邏輯維護(hù)帶來的好處無需言說。
標(biāo)準(zhǔn)庫 itertools 模塊是不應(yīng)該忽視的寶藏。
chain
連接多個(gè)迭代器。
>>> it = chain(xrange(3), "abc")
>>> list(it)
[0, 1, 2, 'a', 'b', 'c']
combinations
返回指定長度的元素順序組合序列。
>>> it = combinations("abcd", 2)
>>> list(it)
[('a', 'b'), ('a', 'c'), ('a', 'd'), ('b', 'c'), ('b', 'd'), ('c', 'd')]
>>> it = combinations(xrange(4), 2)
>>> list(it)
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
combinations_with_replacement 會(huì)額外返回同一元素的組合。
>>> it = combinations_with_replacement("abcd", 2)
>>> list(it)
[('a', 'a'), ('a', 'b'), ('a', 'c'), ('a', 'd'), ('b', 'b'), ('b', 'c'), ('b', 'd'),
('c', 'c'), ('c', 'd'), ('d', 'd')]
compress
按條件表過濾迭代器元素。
>>> it = compress("abcde", [1, 0, 1, 1, 0])
>>> list(it)
['a', 'c', 'd']
條件列表可以是任何布爾列表。
count
從起點(diǎn)開始,"無限" 循環(huán)下去。
>>> for x in count(10, step = 2):
... print x
... if x > 17: break
10
12
14
16
18
cycle
迭代結(jié)束,再從頭來過。
>>> for i, x in enumerate(cycle("abc")):
... print x
... if i > 7: break
a
b
c
a
b
c
a
b
c
dropwhile
跳過頭部符合條件的元素。
>>> it = dropwhile(lambda i: i < 4, [2, 1, 4, 1, 3])
>>> list(it)
[4, 1, 3]
takewhile 則僅保留頭部符合條件的元素。
>>> it = takewhile(lambda i: i < 4, [2, 1, 4, 1, 3])
>>> list(it)
[2, 1]
groupby
將連續(xù)出現(xiàn)的相同元素進(jìn)行分組。
>>> [list(k) for k, g in groupby('AAAABBBCCDAABBCCDD')]
[['A'], ['B'], ['C'], ['D'], ['A'], ['B'], ['C'], ['D']]
>>> [list(g) for k, g in groupby('AAAABBBCCDAABBCCDD')]
[['A', 'A', 'A', 'A'], ['B', 'B', 'B'], ['C', 'C'], ['D'], ['A', 'A'], ['B', 'B'], ['C',
'C'], ['D', 'D']]
ifilter
與內(nèi)置函數(shù) filter() 類似,僅保留符合條件的元素。
>>> it = ifilter(lambda x: x % 2, xrange(10))
>>> list(it)
[1, 3, 5, 7, 9]
ifilterfalse 正好相反,保留不符合條件的元素。
>>> it = ifilterfalse(lambda x: x % 2, xrange(10))
>>> list(it)
[0, 2, 4, 6, 8]
imap
與內(nèi)置函數(shù) map() 類似。
>>> it = imap(lambda x, y: x + y, (2,3,10), (5,2,3))
>>> list(it)
[7, 5, 13]
islice
以切片的方式從迭代器獲取元素。
>>> it = islice(xrange(10), 3)
>>> list(it)
[0, 1, 2]
>>> it = islice(xrange(10), 3, 5)
>>> list(it)
[3, 4]
>>> it = islice(xrange(10), 3, 9, 2)
>>> list(it)
[3, 5, 7]
izip
與內(nèi)置函數(shù) zip() 類似,多余元素會(huì)被拋棄。
>>> it = izip("abc", [1, 2])
>>> list(it)
[('a', 1), ('b', 2)]
要保留多余元素可以用 izip_longest,它提供了一個(gè)補(bǔ)缺參數(shù)。
>>> it = izip_longest("abc", [1, 2], fillvalue = 0)
>>> list(it)
[('a', 1), ('b', 2), ('c', 0)]
permutations
與 combinations 順序組合不同,permutations 讓每個(gè)元素都從頭組合一遍。
>>> it = permutations("abc", 2)
>>> list(it)
[('a', 'b'), ('a', 'c'), ('b', 'a'), ('b', 'c'), ('c', 'a'), ('c', 'b')]
>>> it = combinations("abc", 2)
>>> list(it)
[('a', 'b'), ('a', 'c'), ('b', 'c')]
product
讓每個(gè)元素都和后面的迭代器完整組合一遍。
>>> it = product("abc", [0, 1])
>>> list(it)
[('a', 0), ('a', 1), ('b', 0), ('b', 1), ('c', 0), ('c', 1)]
repeat
將一個(gè)對(duì)象重復(fù) n 次。
>>> it = repeat("a", 3)
>>> list(it)
['a', 'a', 'a']
starmap
按順序處理每組元素。
>>> it = starmap(lambda x, y: x + y, [(1, 2), (10, 20)])
>>> list(it)
[3, 30]
tee
復(fù)制迭代器。
>>> for it in tee(xrange(5), 3):
... print list(it)
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]