現(xiàn)在我們將要向詩(shī)歌下載客戶端添加一些新的處理邏輯,包括在第九部分提到要添加的功能。不過(guò),首先我要說(shuō)明一點(diǎn):我并不知道如何實(shí)現(xiàn)Byronification引擎。那超出了我的編程能力范圍。取而代之的,我想實(shí)現(xiàn)一個(gè)簡(jiǎn)單的功能,即Cummingsifier。其只是將詩(shī)歌內(nèi)容轉(zhuǎn)換成小寫字母:
def cummingsify(poem)
return poem.lower()
這個(gè)方法如此之簡(jiǎn)單以至于它永遠(yuǎn)不會(huì)出錯(cuò)。版本5.0的實(shí)現(xiàn)代碼在twisted-client-5/get-poetry.py文件中。我們使用了修改后的 cummingsify,其會(huì)隨機(jī)地選擇以下行為:
這樣,我們便模擬出來(lái)一個(gè)會(huì)因?yàn)楦鞣N意料不到的問(wèn)題而執(zhí)行失敗的復(fù)雜算法。其它部分的僅有的改變?cè)诜椒╬oetry_main中:
def poetry_main():
addresses = parse_args()
from twisted.internet import reactor
poems = []
errors = []
def try_to_cummingsify(poem):
try:
return cummingsify(poem)
except GibberishError:
raise
except:
print 'Cummingsify failed!'
return poem
def got_poem(poem):
print poem
poems.append(poem)
def poem_failed(err):
print >>sys.stderr, 'The poem download failed.'
errors.append(err)
def poem_done(_):
if len(poems) + len(errors) == len(addresses):
reactor.stop()
for address in addresses:
host, port = address
d = get_poetry(host, port)
d.addCallback(try_to_cummingsify)
d.addCallbacks(got_poem, poem_failed)
d.addBoth(poem_done)
reactor.run()
因此,當(dāng)從服務(wù)器上下載一首詩(shī)歌時(shí),可能會(huì)出現(xiàn)如下情況:
為了實(shí)現(xiàn)下面內(nèi)容的效果,你可以打開多個(gè)服務(wù)器或打開一個(gè)服務(wù)器多次,直到你觀察到所有不同的結(jié)果,當(dāng)然也嘗試一下去連接一個(gè)沒(méi)有服務(wù)器值守的端口。
圖19是我們給deferred添加回調(diào)后形成的callback/errback鏈:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p10_deferred-42.png" alt="" />
注意到,"pass-throug"errback通過(guò)addCallback添加到鏈中。它會(huì)將任何其接收到的Failure傳遞給下一個(gè)errback(即poem_failed函數(shù))。因此poem_failed函數(shù)可以處理來(lái)自get_poetry與try_to_commingsify兩者的failure。下面讓我們來(lái)分析下deferred可能會(huì)出現(xiàn)的激活情況,圖20說(shuō)明了我們能夠下載到詩(shī)歌并且try_to_commingsify成功執(zhí)行的路線圖:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p10_deferred-5.png" alt="" />
在這種情況中,沒(méi)有回調(diào)執(zhí)行失敗,因此控制權(quán)一直在callback中流動(dòng)。注意到poem_done收到的結(jié)果是None,這是因?yàn)樗](méi)有返回任何值。如果我們想讓后續(xù)的回調(diào)都能觸及到詩(shī)歌內(nèi)容,只要顯式地讓got_poem返回詩(shī)歌即可。
圖21說(shuō)明了我們?cè)诔晒ο螺d到詩(shī)歌后,但在try_to_cummingsify中拋出了GibberishError:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p10_deferred-6.png" alt="" />
由于try_to_cummingsify回調(diào)拋出了GibberishError,所以控制權(quán)轉(zhuǎn)移到了errback鏈,即poem_fail回調(diào)被調(diào)用并傳入的捕獲的異常作為其參數(shù)。
由于poem_failed并沒(méi)有拋出獲異?;蚍祷匾粋€(gè)Failure,因此在它執(zhí)行完后,控制權(quán)又回到了callback鏈中。如果我們想讓poem_fail完全處理好傳進(jìn)來(lái)的錯(cuò)誤,那么返回一個(gè)None是再好不過(guò)的做法了。相反,如果我們只想讓poem_failed采取一部分行動(dòng),但繼續(xù)傳遞這個(gè)錯(cuò)誤,那么我們需要改寫poem_failed,即將參數(shù)err作為返回值返回。如此一來(lái),控制權(quán)交給了下一個(gè)errback回調(diào)。
注意到,迄今為止,got_poem與poem_failed都不可能出現(xiàn)執(zhí)行失敗的情況,因此errback鏈上的poem_done是不可能被激活的。但在任何情況下這樣做都是安全的,這體現(xiàn)了"防御式"編程的思想。比如在got_poem或poem_failed出現(xiàn)了bugs,那么這樣做就不會(huì)讓這些bugs的影響進(jìn)入Twisted的核心代碼區(qū)。鑒于上面的描述,可以看出addBoth類似于try/except中的finally語(yǔ)句。
下面我們?cè)賮?lái)看看第三種可能情況,即成功下載到詩(shī)歌但try_to_cummingsify拋出了VauleError,如圖22:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p10_deferred-7.png" alt="" />
除了got_poem得到是原始式樣的詩(shī)歌而不是小寫版的外,與圖20描述的情況完全相同。當(dāng)然,控制權(quán)還是在try_to_cummingsif中進(jìn)行了轉(zhuǎn)移,即使用了try/except捕獲了ValueError并返回了原始式樣的詩(shī)歌。而這一切deferred并不知曉。
最后,我們來(lái)看看當(dāng)試圖連接一個(gè)無(wú)服務(wù)器值守的端口會(huì)出現(xiàn)什么情況,如圖23所示:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p10_deferred-8.png" alt="" />
由于poem_failed返回了一個(gè)None,因此控權(quán)又回到了callback鏈中。
在版本5.0中我們使用普通的try/except來(lái)捕獲try_to_cummingsify中的異常,而沒(méi)有讓deferred來(lái)捕獲這個(gè)異常。這其實(shí)并沒(méi)有什么錯(cuò)誤,但下面我們將采取一種新的方式來(lái)處理異常。
設(shè)想一下,我們讓deferred來(lái)捕獲 GibberishError 與ValueError 異常,并將其傳遞到errback鏈中進(jìn)行處理。如果要保留原有的行為,那么需要下面的errback來(lái)判斷錯(cuò)誤類型是否為Valuerror,如果是,那么返回原始式樣的詩(shī)歌,這樣一來(lái),控制權(quán)再次回到callback鏈中并將原始式樣的詩(shī)歌打印出來(lái)。
但有一個(gè)問(wèn)題:errback并不會(huì)得到原始詩(shī)歌內(nèi)容 。它只會(huì)得到由cummingsify拋出的vauleError異常。為了讓errback處理這個(gè)錯(cuò)誤,我們需要重新設(shè)計(jì)它來(lái)接收到原始式樣的詩(shī)歌。
一種方法是改變cummingsify以讓異常信息中包含原始式樣的詩(shī)歌。這也正是我們?cè)?.1版本中做的,其代碼實(shí)現(xiàn)在twisted-client-5/get-poetry-1.py中。我們改寫ValueError異常為CannotCummingsify異常,其能將詩(shī)歌作為其第一個(gè)參數(shù)來(lái)傳遞。
如果cummingsify是外部模塊中一個(gè)真實(shí)存在的函數(shù),那么其最好是通過(guò)另一個(gè)函數(shù)來(lái)捕獲非GibberishError并拋出一個(gè)CannotCummingsify異常。這樣,我們的poetry_main就成為:
def poetry_main():
addresses = parse_args()
from twisted.internet import reactor
poems = []
errors = []
def cummingsify_failed(err):
if err.check(CannotCummingsify):
print 'Cummingsify failed!'
return err.value.args[0]
return err
def got_poem(poem):
print poem
poems.append(poem)
def poem_failed(err):
print >>sys.stderr, 'The poem download failed.'
errors.append(err)
def poem_done(_):
if len(poems) + len(errors) == len(addresses):
reactor.stop()
for address in addresses:
host, port = address
d = get_poetry(host, port)
d.addCallback(cummingsify)
d.addErrback(cummingsify_failed)
d.addCallbacks(got_poem, poem_failed)
d.addBoth(poem_done)
而新的deferred結(jié)構(gòu)如圖24所示:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p10_deferred-9.png" alt="" />
來(lái)看看cummingsify_failed的errback回調(diào):
def cummingsify_failed(err):
if err.check(CannotCummingsify):
print 'Cummingsify failed!'
return err.value.args[0]
return err
我們使用了Failure中的check方法來(lái)確認(rèn)嵌入在Failure中的異常是否是CannotCummingsify的實(shí)例。如果是,我們返回異常的第一個(gè)參數(shù)(即原始式樣詩(shī)歌)。因此,這樣一來(lái)返回值就不是一個(gè)Failure了,控制權(quán)也就又回到callback鏈中了。否則(即異常不是CannotCummingsify的實(shí)例),我們返回一個(gè)Failure,即將錯(cuò)誤傳遞到下一個(gè)errback中。
圖25說(shuō)明了當(dāng)我們捕獲一個(gè)CannotCummingsify時(shí)的調(diào)用過(guò)程:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p10_deferred-10.png" alt="" />
因此,當(dāng)我們使用deferrd時(shí),可以選擇使用try/except來(lái)捕獲異常,也可以讓deferred來(lái)將異常傳遞到errback回調(diào)鏈中進(jìn)行處理。
在這個(gè)部分,我們?cè)鰪?qiáng)了客戶端的Deferred的功能,實(shí)現(xiàn)了異常與結(jié)果在callback/errback鏈中"路由"。(你可以將各個(gè)回調(diào)看作成路由器,然后根據(jù)傳入?yún)?shù)的情況來(lái)決定其返回值進(jìn)入下一個(gè)stage的哪條鏈,或者說(shuō)控制權(quán)進(jìn)入下一個(gè)stage的哪個(gè)類型的回調(diào))。雖然示例程序是虛構(gòu)出來(lái)的,但它揭示了控制權(quán)在deferred的回調(diào)鏈中交錯(cuò)傳遞具體方向依賴于返回值的類型。
那我們是不是已經(jīng)對(duì)deferred無(wú)所不知了?不,我們還會(huì)在下面的部分繼續(xù)講解deferred的更多的功能。但在第十一部分,我們先不講這部分內(nèi)容,而是實(shí)現(xiàn)我們的Twisted版本的詩(shī)歌下載服務(wù)器。
本部分原作參見(jiàn): dave @ http://krondo.com/?p=1956
本部分翻譯內(nèi)容參見(jiàn)楊曉偉的博客 http://blog.sina.com.cn/s/blog_704b6af70100q87q.html