国产gaysexchina男同gay,japanrcep老熟妇乱子伦视频,吃奶呻吟打开双腿做受动态图,成人色网站,国产av一区二区三区最新精品

7.10 帶額外狀態(tài)信息的回調函數

2018-02-24 15:26 更新

問題

你的代碼中需要依賴到回調函數的使用(比如事件處理器、等待后臺任務完成后的回調等),并且你還需要讓回調函數擁有額外的狀態(tài)值,以便在它的內部使用到。

解決方案

這一小節(jié)主要討論的是那些出現在很多函數庫和框架中的回調函數的使用——特別是跟異步處理有關的。為了演示與測試,我們先定義如下一個需要調用回調函數的函數:

def apply_async(func, args, *, callback):
    # Compute the result
    result = func(*args)

    # Invoke the callback with the result
    callback(result)

實際上,這段代碼可以做任何更高級的處理,包括線程、進程和定時器,但是這些都不是我們要關心的。我們僅僅只需要關注回調函數的調用。下面是一個演示怎樣使用上述代碼的例子:

>>> def print_result(result):
...     print('Got:', result)
...
>>> def add(x, y):
...     return x + y
...
>>> apply_async(add, (2, 3), callback=print_result)
Got: 5
>>> apply_async(add, ('hello', 'world'), callback=print_result)
Got: helloworld
>>>

注意到 print_result() 函數僅僅只接受一個參數 result 。不能再傳入其他信息。而當你想讓回調函數訪問其他變量或者特定環(huán)境的變量值的時候就會遇到麻煩。

為了讓回調函數訪問外部信息,一種方法是使用一個綁定方法來代替一個簡單函數。比如,下面這個類會保存一個內部序列號,每次接收到一個 result 的時候序列號加1:

class ResultHandler:

    def __init__(self):
        self.sequence = 0

    def handler(self, result):
        self.sequence += 1
        print('[{}] Got: {}'.format(self.sequence, result))

使用這個類的時候,你先創(chuàng)建一個類的實例,然后用它的 handler() 綁定方法來做為回調函數:

>>> r = ResultHandler()
>>> apply_async(add, (2, 3), callback=r.handler)
[1] Got: 5
>>> apply_async(add, ('hello', 'world'), callback=r.handler)
[2] Got: helloworld
>>>

第二種方式,作為類的替代,可以使用一個閉包捕獲狀態(tài)值,例如:

def make_handler():
    sequence = 0
    def handler(result):
        nonlocal sequence
        sequence += 1
        print('[{}] Got: {}'.format(sequence, result))
    return handler

下面是使用閉包方式的一個例子:

>>> handler = make_handler()
>>> apply_async(add, (2, 3), callback=handler)
[1] Got: 5
>>> apply_async(add, ('hello', 'world'), callback=handler)
[2] Got: helloworld
>>>

還有另外一個更高級的方法,可以使用協(xié)程來完成同樣的事情:

def make_handler():
    sequence = 0
    while True:
        result = yield
        sequence += 1
        print('[{}] Got: {}'.format(sequence, result))

對于協(xié)程,你需要使用它的 send() 方法作為回調函數,如下所示:

>>> handler = make_handler()
>>> next(handler) # Advance to the yield
>>> apply_async(add, (2, 3), callback=handler.send)
[1] Got: 5
>>> apply_async(add, ('hello', 'world'), callback=handler.send)
[2] Got: helloworld
>>>

討論

基于回調函數的軟件通常都有可能變得非常復雜。一部分原因是回調函數通常會跟請求執(zhí)行代碼斷開。因此,請求執(zhí)行和處理結果之間的執(zhí)行環(huán)境實際上已經丟失了。如果你想讓回調函數連續(xù)執(zhí)行多步操作,那你就必須去解決如何保存和恢復相關的狀態(tài)信息了。

至少有兩種主要方式來捕獲和保存狀態(tài)信息,你可以在一個對象實例(通過一個綁定方法)或者在一個閉包中保存它。兩種方式相比,閉包或許是更加輕量級和自然一點,因為它們可以很簡單的通過函數來構造。它們還能自動捕獲所有被使用到的變量。因此,你無需去擔心如何去存儲額外的狀態(tài)信息(代碼中自動判定)。

如果使用閉包,你需要注意對那些可修改變量的操作。在上面的方案中,nonlocal 聲明語句用來指示接下來的變量會在回調函數中被修改。如果沒有這個聲明,代碼會報錯。

而使用一個協(xié)程來作為一個回調函數就更有趣了,它跟閉包方法密切相關。某種意義上來講,它顯得更加簡潔,因為總共就一個函數而已。并且,你可以很自由的修改變量而無需去使用 nonlocal 聲明。這種方式唯一缺點就是相對于其他Python技術而已或許比較難以理解。另外還有一些比較難懂的部分,比如使用之前需要調用 next() ,實際使用時這個步驟很容易被忘記。盡管如此,協(xié)程還有其他用處,比如作為一個內聯回調函數的定義(下一節(jié)會講到)。

如果你僅僅只需要給回調函數傳遞額外的值的話,還有一種使用 partial() 的方式也很有用。在沒有使用 partial() 的時候,你可能經??吹较旅孢@種使用lambda表達式的復雜代碼:

>>> apply_async(add, (2, 3), callback=lambda r: handler(r, seq))
[1] Got: 5
>>>

可以參考7.8小節(jié)的幾個示例,教你如何使用 partial() 來更改參數簽名來簡化上述代碼。

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號