W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
你的代碼中需要依賴到回調(diào)函數(shù)的使用(比如事件處理器、等待后臺任務(wù)完成后的回調(diào)等),并且你還需要讓回調(diào)函數(shù)擁有額外的狀態(tài)值,以便在它的內(nèi)部使用到。
這一小節(jié)主要討論的是那些出現(xiàn)在很多函數(shù)庫和框架中的回調(diào)函數(shù)的使用——特別是跟異步處理有關(guān)的。為了演示與測試,我們先定義如下一個需要調(diào)用回調(diào)函數(shù)的函數(shù):
def apply_async(func, args, *, callback):
# Compute the result
result = func(*args)
# Invoke the callback with the result
callback(result)
實際上,這段代碼可以做任何更高級的處理,包括線程、進程和定時器,但是這些都不是我們要關(guān)心的。我們僅僅只需要關(guān)注回調(diào)函數(shù)的調(diào)用。下面是一個演示怎樣使用上述代碼的例子:
>>> 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()
函數(shù)僅僅只接受一個參數(shù) result
。不能再傳入其他信息。而當你想讓回調(diào)函數(shù)訪問其他變量或者特定環(huán)境的變量值的時候就會遇到麻煩。
為了讓回調(diào)函數(shù)訪問外部信息,一種方法是使用一個綁定方法來代替一個簡單函數(shù)。比如,下面這個類會保存一個內(nèi)部序列號,每次接收到一個 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()
綁定方法來做為回調(diào)函數(shù):
>>> 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()
方法作為回調(diào)函數(shù),如下所示:
>>> 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
>>>
基于回調(diào)函數(shù)的軟件通常都有可能變得非常復雜。一部分原因是回調(diào)函數(shù)通常會跟請求執(zhí)行代碼斷開。因此,請求執(zhí)行和處理結(jié)果之間的執(zhí)行環(huán)境實際上已經(jīng)丟失了。如果你想讓回調(diào)函數(shù)連續(xù)執(zhí)行多步操作,那你就必須去解決如何保存和恢復相關(guān)的狀態(tài)信息了。
至少有兩種主要方式來捕獲和保存狀態(tài)信息,你可以在一個對象實例(通過一個綁定方法)或者在一個閉包中保存它。兩種方式相比,閉包或許是更加輕量級和自然一點,因為它們可以很簡單的通過函數(shù)來構(gòu)造。它們還能自動捕獲所有被使用到的變量。因此,你無需去擔心如何去存儲額外的狀態(tài)信息(代碼中自動判定)。
如果使用閉包,你需要注意對那些可修改變量的操作。在上面的方案中,nonlocal
聲明語句用來指示接下來的變量會在回調(diào)函數(shù)中被修改。如果沒有這個聲明,代碼會報錯。
而使用一個協(xié)程來作為一個回調(diào)函數(shù)就更有趣了,它跟閉包方法密切相關(guān)。某種意義上來講,它顯得更加簡潔,因為總共就一個函數(shù)而已。并且,你可以很自由的修改變量而無需去使用 nonlocal
聲明。這種方式唯一缺點就是相對于其他Python技術(shù)而已或許比較難以理解。另外還有一些比較難懂的部分,比如使用之前需要調(diào)用 next()
,實際使用時這個步驟很容易被忘記。盡管如此,協(xié)程還有其他用處,比如作為一個內(nèi)聯(lián)回調(diào)函數(shù)的定義(下一節(jié)會講到)。
如果你僅僅只需要給回調(diào)函數(shù)傳遞額外的值的話,還有一種使用 partial()
的方式也很有用。在沒有使用 partial()
的時候,你可能經(jīng)常看到下面這種使用lambda表達式的復雜代碼:
>>> apply_async(add, (2, 3), callback=lambda r: handler(r, seq))
[1] Got: 5
>>>
可以參考7.8小節(jié)的幾個示例,教你如何使用 partial()
來更改參數(shù)簽名來簡化上述代碼。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: