twisted系列教程十七–用inlineCallbacks来管理callbacks
Introduction
在這一部分我們繼續(xù)回到callback.我們將介紹用生成器來寫callbacks.我們會講到這個技巧怎么工作的,還有它和Deferred 的比較.最后我們會用這個技巧重寫我們的poetry client.首先我們要回顧一下生成器是怎樣工作的,人后我們就會明白為什么它是創(chuàng)造callbacks 的一個替代品.
A Brief Review of Generators
你可能已經(jīng)知道,python 的生成器是一個可以重新啟動的函數(shù),你可以用yield來實(shí)現(xiàn)重新啟動的功能.通過yield,這個函數(shù)就變成了一個生成器函數(shù),它會返回一個迭帶器,并可以將一個函數(shù)分成一系列的步驟來運(yùn)行.迭帶器的每一個循環(huán)都會重啟這個函數(shù),這個函數(shù)會繼續(xù)運(yùn)行直到遇到下一個yield.
生成器(和迭帶器)經(jīng)常用來表示生成一系列的返回值.先看一下我們的例子inline-callbacks/gen-1.py:
def my_generator():
????print 'starting up'
????yield 1
????print "workin'"
????yield 2
????print "still workin'"
????yield 3
????print 'done'
for n in my_generator():
????print n
我們有了一個可以生成序列的1,2,3 的生成器.如果你運(yùn)行這個代碼的話,你會發(fā)現(xiàn)generator 里面的輸出和for循環(huán)里面的輸出是交替出現(xiàn)的.
我們可以自己實(shí)現(xiàn)生成器來讓這段代碼更明確,代碼見inline-callbacks/gen-2.py:
def my_generator():
????print 'starting up'
????yield 1
????print "workin'"
????yield 2
????print "still workin'"
????yield 3
????print 'done'
gen = my_generator()
while True:
????try:
????????n = gen.next()
????except StopIteration:
????????break
????else:
????????print n
把它作為一個序列,生成器只是一個可以生產(chǎn)連續(xù)的值的對象.但是我們?nèi)跃涂梢詮纳善鞅旧韥砜词虑?
????生成器函數(shù)不會開始運(yùn)行直到被循環(huán)調(diào)用(用 next 方法)
????一但生成器開始運(yùn)行,它會一直運(yùn)行下去直到它返回到這個循環(huán)(使用yield)
????當(dāng)這個循環(huán)正在運(yùn)行其他的代碼(比如print 語句),這個生成器不會運(yùn)行
????當(dāng)這個生成器在運(yùn)行的時候,這個循環(huán)不會運(yùn)行(它等待generator 并阻塞)
????一但一個生成器失去對循環(huán)的控制, 可能會花任意數(shù)量的時間去執(zhí)行其他的任務(wù)直到生成器再一次運(yùn)行
這有點(diǎn)像callback 在異步程序中的工作方式.我們可以把循環(huán)看成reactor,把生成器看成一系列的callbacks.有意思的地方是,所有的callbacks 都共享同一個命名空間,命名空間從一個callback到另一個callback 得到了延續(xù).
此外,我們可以在同一時間讓多個生成器處于活躍狀態(tài)(例子在inline-callbacks/gen-3.py),讓它們的”callback”互相的交替運(yùn)行,就像你在twisted 中又運(yùn)行了一個獨(dú)立的異步任務(wù).
還漏了一點(diǎn),callback不是被reactor調(diào)用的,callbacks同時也要接受參數(shù).在一個deferred 鏈中,一個callback 或者接收一個結(jié)果,或者接收一個Failure 對象. 自從python2.5 起,生成器被擴(kuò)展成在重啟的時候也可以接收參數(shù),就像inline-callbacks/gen-4.py中闡明的一樣:
class Malfunction(Exception):
????pass
def my_generator():
????print 'starting up'
????val = yield 1
????print 'got:', val
????val = yield 2
????print 'got:', val
????try:
????????yield 3
????except Malfunction:
????????print 'malfunction!'
????yield 4
????print 'done'
gen = my_generator()
print gen.next() # start the generator
print gen.send(10) # send the value 10
print gen.send(20) # send the value 20
print gen.throw(Malfunction())
?# raise an exception inside the generator
try:
????gen.next()
except StopIteration:
????pass
在python2.5 和以后的版本中,yield 聲明是一個求值表達(dá)式.重啟發(fā)生器的代碼可以通過send 方法來獲得這個值,如果你用next 這個值會是None.你也可以在生成器中拋出一個異常.
Inline Callbacks
根據(jù)上面我們講的在生成器中發(fā)送和拋出值和異常的原理,我們可以可以把生成器看成一系列的callbacks,就像deferred 中的那樣,或接收一個正常的結(jié)果或接收一個failure.這些callbacks 被yield 分開,每一個yield 表達(dá)式的值是下一個callback(yield) 的參數(shù).圖像三十五描述了這個過程:
圖片三十五
現(xiàn)在當(dāng)一系列的callback在deferred中組成一個鏈的時候.每一個callback從上一個callback中獲取結(jié)果.這在生成器中很簡單–把你上一次運(yùn)行生成器的的結(jié)果傳到下一次運(yùn)行生成器就可以了.
回想第十三部分我們所講到的,deferred 中的callback 也可以返回deferred 對象,外部的deferred 會保持暫停直到內(nèi)部的deferred 的觸發(fā),然后外部deferred中的下一個callback(errback)會被調(diào)用,并被傳入內(nèi)部deferred 返回的值.
所以想象我們的生成器返回的不是一個普通的python 值,而是一個deferred 對象.當(dāng)這發(fā)生時,外部的deferred 暫停,直到內(nèi)部的deferred觸發(fā).這里相當(dāng)于生成器暫停,并且是是自動的,生成器在一個yield之后會一直暫停直到它被明確的重啟.所以我們可以延遲重啟生成器直到deferred 觸發(fā),在這時我們可以返回正常的值(deferred 正常)或者拋出異常(deferred 失敗).這樣我們的生成器就成為一個真正的異步callback 序列了,這也是inlineCallbacks 函數(shù)要完成的功能.代碼在twisted.internet.defer中.
inlineCallbacks
看一下在inline-callbacks/inline-callbacks-1.py中的例子程序:
from twisted.internet.defer import inlineCallbacks, Deferred
@inlineCallbacks
def my_callbacks():
????from twisted.internet import reactor
????print 'first callback'
????result = yield 1
????# yielded values that aren't deferred come right back
????print 'second callback got', result
????d = Deferred()
????reactor.callLater(5, d.callback, 2)
????result = yield d
????# yielded deferreds will pause the generator
????print 'third callback got', result
????# the result of the deferred
????d = Deferred()
????reactor.callLater(5, d.errback, Exception(3))
????try:
????????yield d
????except Exception, e:
????????result = e
????print 'fourth callback got', repr(result)
???# the exception from the deferred
????reactor.stop()
from twisted.internet import reactor
reactor.callWhenRunning(my_callbacks)
reactor.run()
運(yùn)行這個例子你會看到生成器運(yùn)行到底然后停止reactor.這個例子說明了inlineCallbacks 函數(shù)的幾個方面,首先,inlineCallbacks 是一個裝飾器并用來裝飾生成器函數(shù).inlineCallbacks 的主要的目的就是把一個生成器變成一系列的異步的callbacks.
第二,當(dāng)我們調(diào)用一個用inlineCallbacks 修飾的函數(shù)的時候,我們不需要調(diào)用下一個或者發(fā)送或者拋出我們自己.這個裝飾器會幫我們完成這些并會確保我們的生成器會一直運(yùn)行到底(假設(shè)它并沒有拋出異常).
第三,假如我們在生成器中生成一個不是deferred 的值,生成器會立即重啟并帶著這個yield 生成的值.
最后,如果我們在生成器中生成一個deferred,它會在這個deferred觸發(fā)之后才會重啟.如果這個deferred 成功了,yield 的結(jié)果就是deferred 的結(jié)果.如果這個deferred 失敗了,yield 會拋出這個異常.注意這里的異常是一個普通的Exception 而不是Failure,我們可以用try/except 來捕捉它.
在這個例子中我們僅僅使用了callLater 在一段時間之后去觸發(fā)deferred.這是一個很方便的把非阻塞的延遲放入callback 鏈的方法,一般來說,在我們的生成器中我們會不斷的返回一個已經(jīng)被觸發(fā)過的deferred.
ok,我們現(xiàn)在已經(jīng)知道一個被inlineCallbacks修飾的函數(shù)是怎樣運(yùn)行的,但這個函數(shù)最終會返回什么呢? 你可能已經(jīng)猜到了,會返回deferred.因?yàn)槲覀儾恢郎善魇裁磿r候會停止運(yùn)行,這個被修飾過的函數(shù)是一個異步的函數(shù),最適合返回的是deferred.注意這個返回的deferred 不是yield 語句返回的deferred,它是這個生成器全部運(yùn)行完畢之后才觸發(fā)的deferred.
如果這個生成器拋出一個異常,返回的deferred 會觸發(fā)它的errback 鏈并帶有一個封裝了異常的Failure.如果我們想讓生成器返回一個正常的值,我們必須用defer.returnValue 函數(shù)返回它.像平常的return 語句,它會停止這個生成器(實(shí)際上是拋出了一個特別的異常).inline-callbacks/inline-callbacks-2.py 例子描述了所有的情況.
Client 7.0
讓我們用inlineCallbacks 來寫我們的新的poetry client.你可以在twisted-client-7/get-poetry.py看到代碼.你可能希望和client 6.0 做比較,代碼在這里twisted-client-6/get-poetry.py.他們不一樣的地方在poetry_main 方法中:
def poetry_main():
????addresses = parse_args()
????xform_addr = addresses.pop(0)
????proxy = TransformProxy(*xform_addr)
????from twisted.internet import reactor
????results = []
????@defer.inlineCallbacks
????def get_transformed_poem(host, port):
????????try:
????????????poem = yield get_poetry(host, port)
????????except Exception, e:
????????????print >>sys.stderr, 'The poem download failed:', e
????????????raise
????????try:
????????????poem = yield proxy.xform('cummingsify', poem)
????????except Exception:
????????????print >>sys.stderr, 'Cummingsify failed!'
????????defer.returnValue(poem)
????def got_poem(poem):
????????print poem
????def poem_done(_):
????????results.append(_)
????????if len(results) == len(addresses):
????????????reactor.stop()
????for address in addresses:
????????host, port = address
????????d = get_transformed_poem(host, port)
????????d.addCallbacks(got_poem)
????????d.addBoth(poem_done)
????reactor.run()
我們的inlineCallbacks 生成器函數(shù)get_transformed_poem 負(fù)責(zé)獲取到詩歌和應(yīng)用改變.因?yàn)閮煞N操作都是異步的,我們每一次yield 一個deferred 并等待結(jié)果.和client 6.0 中一樣,如果transformation失敗我們就返回原來的詩.注意我們可以用try/except 在生成器中處理異步的錯誤.
我們可以用和以前一樣的方法測試新的client.首先開啟一個transform server:
python twisted-server-1/tranformedpoetry.py --port 10001
然后開啟兩個poetry server:
python twisted-server-1/fastpoetry.py --port 10002 poetry/fascination.txt
python twisted-server-1/fastpoetry.py --port 10003 poetry/science.txt
現(xiàn)在你可以運(yùn)行我們的新的client 了:
python twisted-client-7/get-poetry.py 10001 10002 10003
Discussion
就像deferred 對象,inlineCallbacks 函數(shù)給了我們一個組織我們的異步的callback 的方法.和deferred 相比,inlineCallbacks 沒有改變游戲規(guī)則(不知道怎樣翻譯貼切).特別的,我們的callback仍是一次只運(yùn)行一個,而且都是reactor觸發(fā)的.我們可以通過在代碼中輸出堆棧信息來確認(rèn)這一點(diǎn),例子代碼在inline-callbacks/inline-callbacks-tb.py.運(yùn)行這個代碼你會得到一個traceback, reactor.run()在上面,中間有很多幫助函數(shù),我們的callback 在下面.
我們可以適當(dāng)?shù)母淖儓D片二十九,它解釋了當(dāng)一個deferred 中的callback 返回了令一個deferred會發(fā)生什么.圖片三十六描述了當(dāng)一個inlineCallbacks 生成器生成一個deferred 會發(fā)生什么:
圖片三十六
這個圖片和圖片二十九很像,因?yàn)樗鼈儽硎龅南敕ㄊ且粯拥抹C一個異步的操作等待令一個.
因?yàn)閕nlineCallbacks 和 deferred 可以解決很多相同的問題,它們兩個該選哪一個呢?下面是inlineCallbacks 可能存在的一些優(yōu)勢:
????可以讓callbacks 共享一個命名空間,不用傳過于的參數(shù)
????callback 的順序很容易看到,因?yàn)樗鼈儚念^執(zhí)行到尾
????有的callback 不用具體的函數(shù)聲明,明確地流程控制,可以讓人少寫代碼
????錯誤可以被我們熟悉的try/except 來處理
?當(dāng)然也有一些陷阱:
????在生成器中的callback不能個別的觸發(fā),這就樣重用代碼變得困難些.而在deferred,組成deferred 的代碼可以任意的加callback
????生成器的形式模糊了一個異步的callbacks是成對(callback/errback)出現(xiàn)的事實(shí).盡管它外表上看起來像一般的序列函數(shù),一個生成器卻表現(xiàn)出一種完全不同的行為.inlineCallbacks 函數(shù)是學(xué)習(xí)異步編程模型必不可少的
Summary
在這一部分我們學(xué)習(xí)了inlineCallbacks 裝飾器和怎樣用inlineCallbacks來組織多個callbacks.
在第十八部分 我們會學(xué)習(xí)一個可以管理多個并行的異步操作的方法.
總結(jié)
以上是生活随笔為你收集整理的twisted系列教程十七–用inlineCallbacks来管理callbacks的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python输出LOGO图标
- 下一篇: CentOS搭建安装SVN