twisted入門教程之十:增強defer功能的客戶端

第十部分:增強defer功能的客戶端

可以從這裏從頭開始閱讀這個系列。


版本5.0

現在我們將要向詩歌下載客戶端添加一些新的處理邏輯,包括在第九部分提到要添加的功能。不過,首先我要說明一點:我並不知道如何實現Byronification引擎。那超出了我的編程能力範圍。取而代之的,我想實現一個簡單的功能,即Cummingsifier。其只是將詩歌內容轉換成小寫字母:

def cummingsify(poem)

    return poem.lower()

這個方法如此之簡單以至於它永遠不會出錯。版本5.0的實現代碼在twisted-client-5/get-poetry.py文件中。我們使用了修改後的cummingsify,其會隨機地選擇以下行爲:

1.返回詩歌的小寫版本

2.拋出一個GibberishError異常

3.拋出一個ValueError

這樣,我們便模擬出來一個會因爲各種意料不到的問題而執行失敗的複雜算法。其它部分的僅有的改變在方法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()

因此,當從服務器上下載一首詩歌時,可能會出現如下情況:

1.打印詩歌的小寫版本

2.打印 ”Cummingsify failed“並附上原始形式的詩歌

3.打印”The peom download failed”

爲了實現下面內容的效果,你可以打開多個服務器或開一個服務器而打開此程序次,直到你觀察到所有不同的結果,當然也嘗試一下去連接一個沒有服務器值守的端口。

19是我們給deferred添加回調後形成的callback/errback鏈:

第十部分:增強defer功能的客戶端

19 deferred中的回調鏈

注意到,"pass-throug”errback通過addCallback添加到鏈中。它會將任何其接收到的Failure傳遞給下一個errback(即poem_failed函數)。因此poem_failed函數可以處理來自get_poetrytry_to_commingsify兩者的failure。下面讓我們來分析下deferred可能會出現的激活情況,圖20說明了我們能夠下載到詩歌並且try_to_commingsify成功執行的路線圖:

第十部分:增強defer功能的客戶端

20 成功下載到詩歌並且成功變換其格式

在這種情況中,沒有回調執行失敗,因此控制權一直在callback中流動。注意到poem_done收到的結果是None,這是因爲它並沒有返回任何值。如果我們想讓後續的回調都能觸及到詩歌內容,只要顯式地讓got_poem返回詩歌即可。

21說明了我們在成功下載到詩歌后,但在try_to_cummingsify中拋出了GibberishError

第十部分:增強defer功能的客戶端

21 成功下載到詩歌但出現了GibberishError

由於try_to_cummingsify回調拋出了GibberishError,所以控制權轉移到了errback鏈,即poem_fail回調被調用並傳入的捕獲的異常作爲其參數。

由於poem_failed並沒有拋出獲異常或返回一個Failure,因此在它執行完後,控制權又回到了callback鏈中。如果我們想讓poem_fail完全處理好傳進來的錯誤,那麼返回一個None是再好不過的做法了。相反,如果我們只想讓poem_failed採取一部分行動,但繼續傳遞這個錯誤,那麼我們需要改寫poem_failed,即將參數err作爲返回值返回。如此一來,控制權交給了下一個errback回調。

注意到,迄今爲止,got_poempoem_failed都不可能出現執行失敗的情況,因此errback鏈上的poem_done是不可能被激活的。但在任何情況下這樣做都是安全的,這體現了“防禦式”編程的思想。比如在got_poempoem_failed出現了bugs,那麼這樣做就不會讓這個bugs的影響進入Twisted的核心代碼區。鑑於上面的描述,可以看出addBoth類型於try/except中的finally語句。

下面我們再來看看第三種可能情況,即成功下載到詩歌但try_to_cummingsify拋出了VauleError,如圖22

第十部分:增強defer功能的客戶端

22:成功下載到詩歌當cummingsify執行失敗

除了got_poem得到是原始式樣的詩歌而不是小寫版的外,與圖20描述的情況完全相同。當然,控制權還是在try_to_cummingsif中進行了轉移,即使用了try/except捕獲了ValueError並返回了原始式樣的詩歌。而這一切deferred並不知曉。

最後,我們來看看當試圖連接一個無服務器值守的端口會出現什麼情況,如圖23所示:

第十部分:增強defer功能的客戶端

23 連接服務器失敗

由於poem_failed返回了一個None,因此控權又回到了callback鏈中。


版本5.1

在版本5.0中我們使用普通的try/except來捕獲try_to_cummingsify中的異常,而沒有讓deferred來捕獲這個異常。這其實並沒有什麼錯誤,但下面我們將採取一種新的方式來處理異常。

設想一下,我們讓deferred來捕獲 GibberishError ValueError 異常,並將其傳遞到errback鏈中進行處理。如果要保留原有的行爲,那麼需要下面的errback來判斷錯誤類型是否爲Valuerror,如果是,那麼返回原始式樣的詩歌,這樣一來,控制權再次回到callback鏈中並將原始式樣的詩歌打印出來。

但有一個問題:errback並不會得到原始詩歌內容 。它只會得到由cummingsify拋出的vauleError異常。爲了讓errback處理這個錯誤,我們需要重新設計它來接收到原始式樣的詩歌。

一種方法是改變cummingsify以讓異常信息中包含原始式樣的詩歌。這也正是我們在5.1版本中做的,其代碼實現在twisted-client-5/get-poetry-1.py中。我們改寫ValueError異常爲CannotCummingsify異常,其能將詩歌作爲其第一個參數來傳遞

如果cummingsify中外部模塊中一個真實存在的函數,那麼其最好是通過另一個函數來捕獲非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所示:

第十部分:增強defer功能的客戶端

24:版本5.1deferrd調用鏈結構

來看看cummingsify_failederrback回調:

def cummingsify_failed(err):

    if err.check(CannotCummingsify):

        print 'Cummingsify failed!'

        return err.value.args[0]

    return err

我們使用了Failure中的check方法來確認嵌入在Failure中的異常是否是CannotCummingsify的實例。如果是,我們返回異常的第一個參數(即原始式樣詩歌)。因此,這樣一來返回值就不是一個Failure了,控制權也就又回到callback鏈中了。否則(即異常不是CannotCummingsify的實例),我們返回一個Failure,即將錯誤傳遞到下一個errback中。

25說明了當我們捕獲一個CannotCummingsify時的調用過程:

第十部分:增強defer功能的客戶端

25:捕獲一個CannotCummingsify異常

因此,當我們使用deferrd時,可以選擇使用try/except來捕獲異常,也可以讓deferred來將異常傳遞到errback回調鏈中進行處理。


總結:

在這個部分,我們增強了客戶端的Deferred的功能,實現了異常與結果在callback/errback鏈中“路由”。(你可以將各個回調看作成路由器,然後根據傳入參數的情況來決定其返回值進入下一個stage的哪條鏈,或者說控制權進入下一個stage的哪個類型的回調)。雖然示例程序是虛構出來的,但它揭示了控制權在deferred的回調鏈中交錯傳遞具體方向依賴於返回值的類型。

那我們是不是已經對deferred無所不知了?不,我們還會在下面的部分繼續講解deferred的更多的功能。但在第十一部分,我們先不講這部分內容,而是實現我們的Twisted版本的詩歌下載服務器。

可以從這裏從頭開始閱讀這個系列。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章