twisted系列教程六–继续重构twisted poetry client
Poetry for Everyone
我們已經在我們的client取得了很大的進步,我們的2.0版本已經試用了Transports,Protocols 和Protocols Factories.但是仍有很多可以提升的地方.2.0 版本的client版本僅僅可以在命令行下載詩.這是因為PoetryClientFactory 不僅僅負責下載詩,也負責在下載完的時候停掉這個程序.這對一個Protocol Factory類來說太奇怪了,它應該只用來創建PoetryProtocols 和 收集已經運行下載完的詩.
我們需要一種可以把這首詩交給我們代碼的方法,但是你必須先獲取這首詩,在一個同步的程序中我們可以這樣做:
def get_poetry(host, post):
????"""Return a poem from the poetry server at the given host and port."""
但是當然的,我們不能在這里這樣做.上面的代碼會阻塞直到這首詩被完全的接收,否則的話它不會像它注釋中說明的一樣.但是這是一個reactive 程序,網絡阻塞可以被它很好的解決.我們需要一個可以在詩下載完的時候通知我們的代碼,以及在下載時不阻塞的方法.這個問題就像twisted 遇到的問題一樣,twisted 需要在一個socket 可以進行I/O 的時候告訴我們.twisted 用callback 的方法很好的解決了這個問題,所以我們也可以這樣用:
def get_poetry(host, port, callback):
????"""
????Download a poem from the given host and port and invoke
??????callback(poem)
????when the poem is complete.
????"""
現在我們有了一個可以讓twisted 使用的api 了,讓我們繼續.
就像我以前說的,我們有時會不按照twisted 的方式的寫代碼,上面的寫法就不是twisted 的寫法,我們會在第七部分和八部分用twisted 的方式來改寫它.用最簡單的方式開始寫代碼可以讓我們更深入的理解.
Client 3.0
你可以看到我們的poety client 3.0 版本在twisted-client-3/get-poetry.py,這個版本有一個get_poetry 方法的實現:
def get_poetry(host, port, callback):
????from twisted.internet import reactor
????factory = PoetryClientFactory(callback)
????reactor.connectTCP(host, port, factory)
需要注意的是我們傳遞callback 給PoetryClientFactory,factory 用這個callback傳遞詩:
class PoetryClientFactory(ClientFactory):
????protocol = PoetryProtocol
????def __init__(self, callback):
????????self.callback = callback
????def poem_finished(self, poem):
????????self.callback(poem)
現在的factory 比client 2.1 版本的簡單多了,因為factory 不用再去關心停掉reactor 了,也少了捕捉錯誤的代碼,我們一會會加上的.而 PoetryProtocol 則不需要做任何改變,我們可以重用它.:
class PoetryProtocol(Protocol):
????poem = ''
????def dataReceived(self, data):
????????self.poem += data
????def connectionLost(self, reason):
????????self.poemReceived(self.poem)
????def poemReceived(self, poem):
????????self.factory.poem_finished(poem)
在這些改變之后,get_poetry,PoetryClientFactory,PoetryProtocol都可以完全重用了.它們都只負責下載詩.所有的初始化,關閉reactor 的邏輯代碼全部在我們的主函數poetry_main:
def poetry_main():
????addresses = parse_args()
????from twisted.internet import reactor
????poems = []
????def got_poem(poem):
????????poems.append(poem)
????????if len(poems) == len(addresses):
????????????reactor.stop()
????for address in addresses:
????????host, port = address
????????get_poetry(host, port, got_poem)
????reactor.run()
????for poem in poems:
????????print poem
我們可以把這些可以重用的部分都放到一個模塊中,然后任何人都可以獲取詩了.^_^.
順便說一下,在你實際的測試client 3.0 的時候,你可以重新配置一下poetry server 讓它一次多輸出一些數據塊.
Discussion
我們可以把詩傳遞的過程用圖片十一 形象化:
圖片十一
圖片十一是值得多想想的,到現在為止我們描述的callback 鏈是以我們自己寫的代碼終止的.但是當你用twisted或者其他的reactive 系統 寫程序的時候,我們的callback 鏈會出現一小段代碼callback另一小段代碼的情況.也就是說,reactive 類型的程序在到達我們寫的代碼的時候不會停止,它會不斷callback 下去.
在你選擇twisted 的時候,請把下面的話記到你的心里.當你做了這個決定之后:
我要用twisted了啦啦啦
你也要做這個決定:
我將要把我的程序構造成由reactor觸發的一系列的callback
也許你現在不會大聲地將它說出來,但twisted 就是這樣的.twisted 就是這樣工作的.可能大多數的python程序是同步的而且大多數的python 模塊也是同步的.假如我們正在寫同步(原文這里寫的是同步,我懷疑有點問題,應該是異步)的程序然后忽然意識到我們需要獲取詩,我們可以使用get_poetry 函數,就像下面的寫法:
...
import poetrylib # I just made this module name up
poem = poetrylib.get_poetry(host, port)
...
然后我們繼續,假如不久以后我們根本不需要詩,然后就可以刪除上面的兩行,對所有的程序都不會造成什么影響.但是假如我們正在寫一個同步的程序然后決定用twisted 版本的額get_poetry,我們就需要用callbacks 來重構我們的程序.我們可能會對代碼改動很多.我并不是說重寫代碼是一個錯誤,根據我們的需求去重構代碼是很有意義的.但它不會只增加幾行代碼那么簡單.簡單來講,同步的和異步的程序不能混合在一起.
如果你對twisted 和異步編程了解不是很多,我還是建議你開始研究大型的twisted 程序代碼庫之前自己先實現幾個簡單的demo. 這樣的話你會在沒有其他復雜的干擾下找到twisted 的感覺.假如你的程序已經是異步的,和twisted結合起來就會相對簡單.twisted 和 pyGTK 和 pyQT 就結合的很好.
When Things Go Wrong
在client 3.0 版本中我們不再監測當連接服務器時出現的錯誤,就像在client 1.0 版本中的那樣.假如我們讓client 3.0 從一個不存在的server上下載詩的話,client 3.0 不會崩潰掉而是在原地不停的等待.clientConnectionFailed callback 仍舊會被調用,但是ClientFactory中的默認clientConnectionFailed 什么也不做,所以got_poem callback 永遠不會被調用,reactor 永遠不會停,然后我們又成功的寫了另一個什么也不做的程序.
很明顯的我們需要來處理這個錯誤,但是在哪里呢?錯誤信息通過clientConnectionFailed 被傳遞到factory,所以我們從這里開始,但是這個factory 應該是可用的,正常的處理錯誤的方法應該依據factory被調用的地方的上下文來處理.在一些程序中,接收不到詩歌可能會是一個災難,在令一些程序中,我們仍舊可以繼續運行(這里是在說twisted 的容錯性比較強).
換句話說,當你用get_poetry 的時候需要知道什么時候會出錯,不僅僅是什么時候是對的.在一個同步的系統中,get_poetry 會拋出一個異常,然后用一個try/except 進行捕捉,但是在一個reactive 的系統里,錯誤信息也必須以異步的方式傳遞.畢竟我們直到get_poetry 返回的時候我們才能發現連接錯誤了,下面是一種可能的情況:
def get_poetry(host, port, callback):
????"""
????Download a poem from the given host and port and invoke
??????callback(poem)
????when the poem is complete. If there is a failure, invoke:
??????callback(None)
????instead.
????"""
通過監測callback 的參數,client可以確定是否我們最終得到了一首詩.這樣就可以防止我們的程序永遠運行下去,但是還是會有一些小問題,當你向callback傳遞None的時候,并不能概括到所有的出錯信息,而且twisted 的一些api 也會默認的返回None,所以這里我們要用err 參數來替代None,err中可以包含具體的出錯信息.就像下面的一樣:
def get_poetry(host, port, callback):
????"""
????Download a poem from the given host and port and invoke
??????callback(poem)
????when the poem is complete. If there is a failure, invoke:
??????callback(err)
????instead, where err is an Exception instance.
????"""
如果這里用一個異常就基本上和我們的同步程序一樣了.現在我們可以從異常中獲取出錯的信息.正常的,在我們在平常的python代碼中如果遇到了異常我們會輸出traceback供我們調試用.
請記住在我們的callback被觸發的時候我們并不想要一個traceback.我們真正想要的是在出現異常的地方的exception實例 和 當時的traceback.
twisted 包含了一個叫做Failure 的抽象,failure是Exception 和 traceback 的封裝.Failure 文檔描述了怎樣創建一個failure.通過傳遞給callback一個Failure對象,我們可以很好的保護traceback 信息.
在 twisted-failure/failure-examples.py中有一些Failure 對象的用法,它演示了Failure 是怎樣保護traceback信息的,即使在一個except 代碼塊上下文之外.我們現在不會在怎樣建立Failure 實例上花費很多時間,在第七部分,我們會看到Failure 的用法.
第三個版本的get_poetry:
def get_poetry(host, port, callback):
????"""
????Download a poem from the given host and port and invoke
??????callback(poem)
????when the poem is complete. If there is a failure, invoke:
??????callback(err)
????instead, where err is a twisted.python.failure.Failure instance.
????"""
在這個版本后,我們在得到異常的時候還能同時得到一個traceback 記錄.
我們已經快完成了,但是還有一個問題.處理錯誤和處理正常的結果看起來是一種很奇怪的行為.一般來說,我們會對出錯和正常結果做出完全不同的操作.在同步的系統中我們會用try/except 語句來分別處理正確的和錯誤的結果:
try:
????attempt_to_do_something_with_poetry()
except RhymeSchemeViolation:
????# the code path when things go wrong
else:
????# the code path when things go so, so right baby
如果我們也想保持這種錯誤處理的方式,我們需要讓錯誤處理走另一條路徑.在異步程序中分出一個路徑意味著多出一個callback.
def get_poetry(host, port, callback, errback):
????"""
????Download a poem from the given host and port and invoke
??????callback(poem)
????when the poem is complete. If there is a failure, invoke:
??????errback(err)
????instead, where err is a twisted.python.failure.Failure instance.
????"""
Client 3.1
client 3.1 在twisted-client-3/get-poetry-1.py 中,變化還是非常直觀的, PoetryClientFactory 會獲得一個callback 和一個errback,在clientConnectionFailed中調用了errback.
class PoetryClientFactory(ClientFactory):
????protocol = PoetryProtocol
????def __init__(self, callback, errback):
????????self.callback = callback
????????self.errback = errback
????def poem_finished(self, poem):
????????self.callback(poem)
????def clientConnectionFailed(self, connector, reason):
????????self.errback(reason)
既然clientConnectionFailed已經接收到一個包含錯誤信息的Failure 對象,我們把它傳給errback 就可以了.其他的改變就很小了,我們就略去不講了,你可以在不啟動server 的情況下測試client 3.1:
python twisted-client-3/get-poetry-1.py 10004
你會看到下面的一些輸出:
Poem failed: [Failure instance: Traceback (failure with no frames): : Connection was refused by other side: 111: Connection refused.
]
輸出是從我們的poem_failed errback 輸出的,在這種情況下,twisted 僅僅傳遞給我們一個Exception 而不是拋出,所以我們在這里不會得到一個traceback.但是一個traceback 不是必須的,因為這個地方不是一個bug.只是twisted 告訴我們,我們不能連接到那個地址.
Summary
下面是我們從第六部分學到的:
????我們為twisted程序寫的api必須是異步的
????我們不能將同步的代碼和異步的代碼混合
????在我們的代碼中不能不用callback,就像twisted 那樣
????我們不得不用callback去處理錯誤
????是不是意味著我們用twisted 寫的每一個api都要包含callback和errback 兩個參數?這樣聽起來可不是太美好.幸運的是twisted 已經用一個抽象把這兩個參數消除掉了并帶來一些新的特性.我們將在第七部分講到.
?
總結
以上是生活随笔為你收集整理的twisted系列教程六–继续重构twisted poetry client的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 字符串常用操作(比较、查找位置
- 下一篇: from __future__ impo