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