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