twisted系列教程八–延迟的诗
Client 4.0
既然我們已經對deferred有些了解了,我們可以用deferred 來重寫我們的poetry client,你可以在這里找到client 4.0 twisted-client-4/get-poetry.py.
我們的get_poetry 函數不再需要callback 和 errback這兩個參數.相反的,它返回一個deferred,我們可以在它上面附加一些callback 和errback.
def get_poetry(host, port):
????"""
????Download a poem from the given host and port. This function
????returns a Deferred which will be fired with the complete text of
????the poem or a Failure if the poem could not be downloaded.
????"""
????d = defer.Deferred()
????from twisted.internet import reactor
????factory = PoetryClientFactory(d)
????reactor.connectTCP(host, port, factory)
????return d
我們的factory 對象初始化的時候不再傳入callback/errback 對,而是傳入一個deferred 對象.一但我們得到詩或者我們發現我們不能連接上server,deferred 就會被觸發–帶著一首詩 或者錯誤.
class PoetryClientFactory(ClientFactory):
????protocol = PoetryProtocol
????def __init__(self, deferred):
????????self.deferred = deferred
????def poem_finished(self, poem):
????????if self.deferred is not None:
????????????d, self.deferred = self.deferred, None
????????????d.callback(poem)
????def clientConnectionFailed(self, connector, reason):
????????if self.deferred is not None:
????????????d, self.deferred = self.deferred, None
????????????d.errback(reason)
注意在deferred被觸發之后我們釋放deferred 的方法.這是一個在twisted源代碼中使用的模式,可以幫助我們確保我們不會觸發相同的deferred兩次.也可以讓python 的垃圾回收機更容易的回收資源.
在一次的,我們也不需要修改PoetryProtocol,還有一個需要修改的地方是poetry_main 函數:
def poetry_main():
????addresses = parse_args()
????from twisted.internet import reactor
????poems = []
????errors = []
????def got_poem(poem):
????????poems.append(poem)
????def poem_failed(err):
????????print >>sys.stderr, 'Poem failed:', err
????????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.addCallbacks(got_poem, poem_failed)
????????d.addBoth(poem_done)
????reactor.run()
????for poem in poems:
????????print poem
注意我們是怎樣利用deferred 的callback 鏈來重構poem_done. 因為deferred在twisted中應用的這么頻繁,所以我們經常用一個字符d 來持有你正在用的deferred對象,如果要做長期的存儲對象,比如作為一個對象的屬性,就經常被叫做”deferred”.
Discussion
我們的新的client 的get_poetry 和我們之前寫的同步的版本的get_poetry 接收相同的參數–poetry server 的地址.同步的版本返回一首詩,而我們的異步版本返回一個deferred.返回deferred對象是twisted 所有特有的,這就指出了deferred的另一種概念:
一個Deferred對象描述了一個"異步的結果"或者說"還沒有到來的結果"
我們可以用下面的圖片描述這兩種編程的模式:
圖片十三
通過返回一個deferred,異步的api可以返回用戶如下的信息:
我是一個異步的方法.你想讓我做的事情我現在還沒有做,但是當我做完的時候,我會觸發這個deferred的callback鏈,并傳遞返回結果.
當然,這個方法本身不會迭代的觸發deferred,它已經返回了.而是這個方法已經在返回的結果上動態的裝置了一系列的事件,然后最終導致deferred被觸發.
所以deferred 實際上是一種時間移位的方法,能讓一個函數的返回結果來適應異步模型的需要.一個函數返回一個deferred 意味著這個函數是異步的,一種將來再返回結果的表現,一個結果會延遲的承諾.
一個同步的函數返回deferred也是可能的,技術上講,deferred 返回一個值意味著這個函數可能是異步的,我們將會看到同步的函數返回deferred 的例子
因為deferred 的行為是很明確和被很多人知道的,如果你寫的apis也返回deferred 你的程序會很容易的被理解和被復用的.如果沒有deferred,每一個twisted 的程序或者每一個twisted 的組件可能都會有它們自己的唯一的用來處理callback 的方法,這樣會增加你的學習成本.
When You’re Using Deferreds, You’re Still Using Callbacks, and They’re Still Invoked by the Reactor
當你一開始學習twisted 的時候,一個經常范的錯誤就是向deferred中添加很多的callback,特別的,人們經常認為向一個函數中加入足夠多的callback它就是異步的了.這會導致你認為你可以在callback中用os.system,它就不是非阻塞的了.
我認為這個錯誤是因為你還沒有真正的理解異步模型.因為典型的twisted 代碼用了很多的deferred 而且很少會用到reactor,他導致你認為deferred做了全部的工作.如果你是從一開始就讀的這個系列,你就會明白遠不是這種情況.盡管twisted 是由很多工作在一起的部分組成的,但實現異步模型的任務是reactor 來完成的.deferred 是個很重要的抽象,但是我們不用它也可以寫我們的twisted client.
讓我們看一下我們第一個callback被觸發時候的堆棧信息.運行twisted-client-4/get-poetry-stack.py(記得運行server),你會看到下面的輸出:
File "twisted-client-4/get-poetry-stack.py", line 129, in
poetry_main()
File "twisted-client-4/get-poetry-stack.py", line 122, in poetry_main
reactor.run()
... # some more Twisted function calls
protocol.connectionLost(reason)
File "twisted-client-4/get-poetry-stack.py", line 59, in connectionLost
self.poemReceived(self.poem)
File "twisted-client-4/get-poetry-stack.py", line 62, in poemReceived
self.factory.poem_finished(poem)
File "twisted-client-4/get-poetry-stack.py", line 75, in poem_finished
d.callback(poem) # here's where we fire the deferred
... # some more methods on Deferreds
File "twisted-client-4/get-poetry-stack.py", line 105, in got_poem
traceback.print_stack()
和client 2.0 版本的堆棧信息非常相像,我們可以用圖片十四來形象的描述:
?
圖片十四
和我們前一個版本的client非常相像,(看到這張圖片你想到了什么,哈哈,作者表達的既幽默又隱諱:for the sake of the children).這張圖片沒有表達出的一點是:callback 鏈不會把控制權返回給reactor,直到第二個callback被觸發,也就是在第一個callback返回結果之后.
client4.0 和client2.0 中輸出的堆棧信息中有一點不同的是,"twisted code " 和"our code" 的界限變得模糊了,自從deferred 的方法之后是真正的twisted code.這種"twisted code" 和 "our code" 的交互在大型的twisted 項目中是非常常見的.
通過在twisted中使用deferred我們已經在callback 鏈中多增加了幾步,但是我們改變異步模型的基本原理.回想一下callback 程序的一些事實:
????在任一時間只有一個callback在運行
????當reactor 在運行的時候,我們的程序暫停
????和上一條相反,當我們的程序在運行的時候,reactor暫停
????如果callback阻塞了,則整個程序都會阻塞
?
向deferred上多增加一個callback不會改變這些事實.特別地,一個阻塞的callback仍舊會阻塞即使它被加進一個deferred中.所以那個deferred被觸發的時候也會阻塞,則整個程序都是阻塞的.我們得出下面的結論: deferred 是一個管理callbacks 的方法,它們不是一個可以避免阻塞 和 可以把阻塞變成非阻塞的方法
我們可以通過構建一個帶有阻塞callback 的deferred來證明最后一點.看一下這個例子:twisted-deferred/defer-block.py.第二個callback用time.sleep 進行阻塞.如果你運行那個程序然后檢查print語句的順序,它非常明確的說明了一個阻塞的callback也會在deferred中阻塞.
Summary
通過返回一個deferred,一個函數告訴用戶我是異步的,并提供了一個機制去獲得異步的結果.deferred 在twisted 中應用非常廣泛,如果你查看twisted 的api,你會發現它.所以熟悉deferred 會給你帶來回報的.Client 4.0 是第一個用twisted 范寫出來的,使用deferred作為一個異步函數的返回值.還有一些twisted api 可以讓我們把它變得更干凈些,但是我想它已經描述了怎么用twisted寫程序. 最后我們還會用twisted來寫我們的server.
但是我們跟deferred還沒完.deferred 類 用很短的一段代碼提供了很多有特色的api.我們將會講更多的deferred 的特色,請關注第九部分.
?
?
?
?
?
總結
以上是生活随笔為你收集整理的twisted系列教程八–延迟的诗的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python twisted教程 二:缓
- 下一篇: Python爬虫之xpath的详细使用(