在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/ 構(gòu)造"回調(diào)"的另一種方法
小插曲 Deferred
異步編程模式與Reactor初探
使用Deferred新功能實現(xiàn)新客戶端
由twisted支持的客戶端
增強defer功能的客戶端
改進詩歌下載服務(wù)器
測試詩歌
更加"抽象"的運用Twisted
Deferred用于同步環(huán)境
輪子內(nèi)的輪子: Twisted和Erlang
Twisted 進程守護
構(gòu)造"回調(diào)"的另一種方法
Twisted 理論基礎(chǔ)
惰性不是遲緩: Twisted和Haskell
第二個小插曲,deferred
使用Deferred的詩歌下載客戶端
Deferreds 全貌
結(jié)束
取消之前的意圖
由Twisted扶持的客戶端
改進詩歌下載服務(wù)器
初識Twisted

構(gòu)造"回調(diào)"的另一種方法

簡介

這部分我們將回到"回調(diào)"這個主題.我們將介紹另外一種寫回調(diào)函數(shù)的方法,即在Twisted中使用 generators. 我們將演示如何使用這種方法并且與使用"純" Deferreds 進行對比. 最后, 我們將使用這種技術(shù)重寫詩歌客戶端. 但首先我們來回顧一下 generators 的工作原理,以便弄清楚它為何是創(chuàng)建回調(diào)的候選方法.

簡要回顧生成器

你可能知道, 一個Python生成器是一個"可重啟的函數(shù)",它是在函數(shù)體中用 yield 語句創(chuàng)建的. 這樣做可以使這個函數(shù)變成一個"生成器函數(shù)",它返回一個"iterator"可以用來以一系列步驟運行這個函數(shù). 每次迭代循環(huán)都會重啟這個函數(shù),繼續(xù)執(zhí)行到下一個 yield 語句.

生成器(和迭代器)通常被用來代表以惰性方式創(chuà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

這里我們用生成器創(chuàng)建了1,2,3序列. 如果你運行這些代碼,會看到在生成器上做迭代時,生成器中的 print 與循環(huán)語句中的 print 語句交錯出現(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

把它視作一個序列,生成器僅僅是獲取連續(xù)值的一個對象.但我們也可以以生成器本身的角度看問題:

  1. 生成器函數(shù)在被循環(huán)調(diào)用之前并沒有執(zhí)行(使用 next 方法).
  2. 一旦生成器開始運行,它將一直執(zhí)行直到返回"循環(huán)"(使用 yield)
  3. 當循環(huán)中運行其他代碼時(如 print 語句),生成器則沒有運行.
  4. 當生成器運行時, 則循環(huán)沒有運行(等待生成器返回前它被"阻滯"了).
  5. 一旦生成器將控制交還到循環(huán),再啟動可能需要等待任意時間(其間任意量的代碼可能被執(zhí)行).

這與異步系統(tǒng)中的回調(diào)工作方式非常類似. 我們可以把 while 循環(huán)視作 reactor, 把生成器視作一系列由 yield 語句分隔的回調(diào)函數(shù). 有趣的是, 所有的回調(diào)分享相同的局部變量名空間, 而且名空間在不同回調(diào)中保持一致.

進一步,你可以一次激活多個生成器(參考例子 inline-callbacks/gen-3.py),使得它們的"回調(diào)"互相交錯,就像在Twisted系統(tǒng)中獨立運行的異步程序.

然而,這種方法還是有一些欠缺.回調(diào)不僅僅被 reactor 調(diào)用, 它還能接受信息.作為 deferred 鏈的一部分,回調(diào)要么接收Python值形式的一個結(jié)果,要么接收 Failure 形式的一個錯誤.

從Python2.5開始,生成器功能被擴展了.當你再次啟動生成器時,可以給它發(fā)送信息,如 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 語句是一個計算值的表達式.重新啟動生成器的代碼可以使用 send 方法代替 next 決定它的值(如果使用 next 則值為 None), 而且你還可以在迭代器內(nèi)部使用 throw 方法拋出任何異常. 是不是很酷?

內(nèi)聯(lián)回調(diào)

根據(jù)我們剛剛回顧的可以向生成器發(fā)送值或拋出異常的特性,可以設(shè)想它是像 deferred 中的一系列回調(diào),即可以接收結(jié)果或錯誤. 每個回調(diào)被 yield 分隔,每一個 yield 表達式的值是下一個回調(diào)的結(jié)果(或者 yield 拋出異常表示錯誤).圖35顯示相應(yīng)概念:

http://wiki.jikexueyuan.com/project/twisted-intro/images/p17_generator-callbacks1.png" alt="" />

現(xiàn)在一系列回調(diào)以 deferred 方式被鏈接在一起,每個回調(diào)從它前面的回調(diào)接收結(jié)果.生成器很容易做到這一點——當再次啟動生成器時,僅僅使用 send 發(fā)送上一次調(diào)用生成器的結(jié)果( yield 產(chǎn)生的值).但這看起來有點笨,既然生成器從開始就計算這個值,為什么還需要把它發(fā)送回來? 生成器可以將這個值儲存在一個變量中供下一次使用. 因此這到底是為什么呢?

回憶一下我們在第十三節(jié)中所學, deferred 中的回調(diào)還可以返回 deferred 本身. 在這種情況下, 外層的 deferred 先暫停等待內(nèi)層的 deferred 激發(fā),接下來外層 deferred 鏈使用內(nèi)層 deferred 的返回結(jié)果(或錯誤)激發(fā)后續(xù)的回調(diào)(或錯誤回調(diào)).

所以設(shè)想我們的生成器生成一個 deferred 對象而不是一個普通的Python值. 這時生成器會自動"暫停";生成器總是在每個 yield 語句后暫停直到被顯示的重啟.因而我們可以延遲它的重啟直到 deferred 被激發(fā), 屆時我們會使用 send 方法發(fā)送值(如果 deferred 成功)或者拋出異常(如果 deferred 失敗).這就使我們的生成器成為一個真正的異步回調(diào)序列,這正是 twisted.internet.deferinlineCallbacks 函數(shù)背后的概念.

進一步討論內(nèi)聯(lián)回調(diào)

考慮以下例程, 位于 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()

運行這個例子可以看到生成器運行到最后并終止了 reactor, 這個例子展示了 inlineCallbacks 函數(shù)的很多方面.

首先, inlineCallbacks 是一個修飾器,它總是修飾生成器函數(shù),如那些使用 yield 語句的函數(shù). inlineCallbacks 唯一的目的是將一個生成器按照上述策略轉(zhuǎn)化為一系列異步回調(diào).

第二,當我們調(diào)用一個用 inlineCallbacks 修飾的函數(shù)時,不需要自己調(diào)用 sendthrow 方法.修飾符會幫助我們處理細節(jié),并確保生成器運行到結(jié)束(假設(shè)它不拋出異常).

第三,如果我們從生成器生成一個非延遲值,它將以 yield 生成的值立即重啟.

最后,如果我們從生成器生成一個 deferred,它不會重啟除非此 deferred 被激發(fā).如果 deferred 成功返回,則 yield 的結(jié)果就是 deferred 的結(jié)果.如果 deferred 失敗了,則 yield 會拋出異常. 注意這個異常僅僅是一個普通的 Exception 對象,而不是 Failure,我們可以在 yield 外面用 try/except 塊捕獲它們.

在上面的例子中,我們僅用 callLater 在一小段時間之后去激發(fā) deferred.雖然這是一種將非阻塞延遲放入回調(diào)鏈的實用方法,但通常我們會生成一個 deferred,它是被生成器中其他的異步操作(如 get_poetry)返回的.

OK,現(xiàn)在我們知道了 inlineCallbacks 修飾的函數(shù)是如何運行的,但當你實際調(diào)用時會返回什么值呢?正如你認為的,將得到 deferred.由于不能確切地知道生成器何時停止(它可能生成一個或多個 deferred),裝飾函數(shù)本身是異步的,所以 deferred 是一個合適的返回值.注意這個 返回的deferred 并不是生成器中 yield 生成的 deferred.相反,它在生成器完全結(jié)束(或拋出異常)后才被激發(fā).

如果生成器拋出一個異常,那么返回的 deferred 將激發(fā)它的錯誤回調(diào)鏈,把異常包含在一個 Failure 中. 但是如果我們希望生成器返回一個正常值,必須使用 defer.returnValue 函數(shù). 像普通 return 語句一樣,它也會終止生成器(實際會拋出一個特殊異常).例子 inline-callbacks/inline-callbacks-2.py 說明了這兩種可能.

客戶端7.0

讓我們在新版本的詩歌客戶端中加入 inlineCallbacks,你可以在 twisted-client-7/get-poetry.py 中查看源代碼.也許你需要與客戶端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 負責取回詩歌并且進行轉(zhuǎn)換(通過轉(zhuǎn)換服務(wù)).由于這兩個操作都是異步的,我們每次生成一個 deferred 并且隱式地等待結(jié)果.與客戶端6.0一樣,如果變換失敗則返回原始詩歌.我們可以使用 try/except 語句捕獲生成器中的異步錯誤.

我們以先前的方式測試新版客戶端. 首先啟動一個變換服務(wù):

python twisted-server-1/tranformedpoetry.py --port 10001

然后啟動兩個詩歌服務(wù)器:

python twisted-server-1/fastpoetry.py --port 10002 poetry/fascination.txt
python twisted-server-1/fastpoetry.py --port 10003 poetry/science.txt

現(xiàn)在可以運行新的客戶端:

python twisted-client-7/get-poetry.py 10001 10002 10003

試試關(guān)閉一個或多個服務(wù)器,看一看客戶端如何捕獲錯誤.

討論

就像 Deferred 對象, inlineCallbacks 函數(shù)給我們一種組織異步回調(diào)的新方式.使用 deferred, inllineCallbacks 并不會改變游戲規(guī)則.我們的回調(diào)仍然一次調(diào)用一個回調(diào),它們?nèi)匀槐?reactor 調(diào)用.我們可以通過打印內(nèi)聯(lián)回調(diào)的回溯堆棧信息來證實這一點,參見腳本 inline-callbacks/inline-callbacks-tb.py.運行此代碼你將首先獲得一個關(guān)于 reactor.run() 的回溯,然后是許多幫助函數(shù)信息,最后是我們的回調(diào).

圖29解釋了當 deferred 中一個回調(diào)返回另一個 deferred 時會發(fā)生什么,我們調(diào)整它來展示當一個 inlineCallbacks 生成器生成一個 deferred 時會發(fā)生什么,參考圖36:

http://wiki.jikexueyuan.com/project/twisted-intro/images/p17_inline-callbacks1.png" alt="" /> 圖36 inlineCallbacks 函數(shù)中的控制流

同樣的圖對兩種情況都適用,因為它們表示的想法都是一樣的 —— 一個異步操作正在等待另一個操作.

由于 inlineCallbacksdeferred 解決許多相同的問題,在它們之間如何選擇呢?下面列出一些 inlineCallbacks 的潛在優(yōu)勢.

  • 由于回調(diào)共享同一個命名空間,因此沒有必要傳遞額外狀態(tài).
  • 回調(diào)的順序很容易看到,因為它總是從上到下執(zhí)行.
  • 節(jié)省了每個回調(diào)函數(shù)的聲明和隱式控制流,通常可以減少輸入工作量.
  • 可以使用熟悉的 try/except 語句處理錯誤.

當然也存在一些缺陷:

  • 生成器中的回調(diào)不能被單獨調(diào)用,這使代碼重用比較困難.而構(gòu)造 deferred 的代碼則能夠以任意順序自由地添加任何回調(diào).
  • 生成器的緊致性可能混淆一個事實,其實異步回調(diào)非?;逎?盡管生成器看起來像一個普通的函數(shù)序列,但是它的行為卻非常不一樣. inlineCallbacks 函數(shù)不是一種避免學習異步編程模型的方式.

就像任何技術(shù),實踐將積累出必要的經(jīng)驗,幫你做出明智選擇.

總結(jié)

在這個部分,我們學習了 inlineCallbacks 裝飾器以及它怎樣使我們能夠以Python生成器的形式表達一系列異步回調(diào).

在第十八節(jié)中,我們將學習一種管理 一組 "并行"異步操作的技術(shù).

參考練習

  1. 為什么 inlineCallbacks 函數(shù)是復數(shù)(形式)?
  2. 研究 inlineCallbacks 的實現(xiàn)以及它們幫助函數(shù) _inlineCallbacks. 并思考短語"魔鬼在細節(jié)處".
  3. 有N個 yield 語句的生成器中包含多少個回調(diào),假設(shè)其中沒有循環(huán)或者 if 語句?
  4. 詩歌客戶端7.0可能同時運行三個生成器.概念上,它們之間有多少種不同的交錯方式?考慮詩歌客戶端和 inlineCallbacks 的實現(xiàn),你認為實際有多少種可能?
  5. 把客戶端7.0中的 got_poem 放入到生成器中.
  6. poem_done 回調(diào)放入生成器.小心!確保處理所有失敗情況以便無論怎樣 reactor 都會關(guān)閉.與使用 deferred 關(guān)閉 reactor 對比代碼有何不同?
  7. 一個在 while 循環(huán)中使用 yield 語句的生成器代表一個概念上的無限序列.那么同樣的裝飾有 inlineCallbacks 的生成器又代表什么呢?

參考

本部分原作參見: dave @ http://krondo.com/blog/?p=2441

本部分翻譯內(nèi)容參見luocheng @ https://github.com/luocheng/twisted-intro-cn/blob/master/p17.rst