欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python的Twisted框架中使用Deferred對(duì)象來管理回調(diào)函數(shù)

 更新時(shí)間:2016年05月25日 11:20:27   作者:Q_AN1314  
當(dāng)說起Twisted的異步與非阻塞模式等特性時(shí),回調(diào)函數(shù)的使用在其中自然就顯得不可或缺,接下來我們就來看Python的Twisted框架中使用Deferred對(duì)象來管理回調(diào)函數(shù)的用法.

首先拋出我們?cè)谟懻撌褂没卣{(diào)編程時(shí)的一些觀點(diǎn):

  • 激活errback是非常重要的。由于errback的功能與except塊相同,因此用戶需要確保它們的存在。他們并不是可選項(xiàng),而是必選項(xiàng)。
  • 不在錯(cuò)誤的時(shí)間點(diǎn)激活回調(diào)與在正確的時(shí)間點(diǎn)激活回調(diào)同等重要。典型的用法是,callback與errback是互斥的即只能運(yùn)行其中一個(gè)。
  • 使用回調(diào)函數(shù)的代碼重構(gòu)起來有些困難。

Deferred
Twisted使用Deferred對(duì)象來管理回調(diào)函數(shù)的序列。有些情況下可能要把一系列的函數(shù)關(guān)聯(lián)到Deferred對(duì)象上,以便在在異步操作完成時(shí)按次序地調(diào)用(這些一系列的回調(diào)函數(shù)叫做回調(diào)函數(shù)鏈);同時(shí)還要有一些函數(shù)在異步操作出現(xiàn)異常時(shí)來調(diào)用。當(dāng)操作完成時(shí),會(huì)先調(diào)用第一個(gè)回調(diào)函數(shù),或者當(dāng)錯(cuò)誤發(fā)生時(shí),會(huì)先調(diào)用第一個(gè)錯(cuò)誤處理回調(diào)函數(shù),然后Deferred對(duì)象會(huì)把每個(gè)回調(diào)函數(shù)或錯(cuò)誤處理回調(diào)函數(shù)的返回值傳遞給鏈中的下一個(gè)函數(shù)。
Callbacks
一個(gè)twisted.internet.defer.Deferred對(duì)象代表著在將來某個(gè)時(shí)刻一定會(huì)產(chǎn)生結(jié)果的一個(gè)函數(shù)。我們可以把一個(gè)回調(diào)函數(shù)關(guān)聯(lián)到Deferred對(duì)象上,一旦這個(gè)Deferred對(duì)象有了結(jié)果,這個(gè)回調(diào)函數(shù)就會(huì)被調(diào)用。另外,Deferred對(duì)象還允許開發(fā)者為它注冊(cè)一個(gè)錯(cuò)誤處理回調(diào)函數(shù)。Deferred機(jī)制對(duì)于各種各樣的阻塞或者延時(shí)操作都為開發(fā)者提供了標(biāo)準(zhǔn)化的接口。
from twisted.internet import reactor, defer

def getDummyData(inputData):
  """
  This function is a dummy which simulates a delayed result and
  returns a Deferred which will fire with that result. Don't try too
  hard to understand this.
  """
  print('getDummyData called')
  deferred = defer.Deferred()
  # simulate a delayed result by asking the reactor to fire the
  # Deferred in 2 seconds time with the result inputData * 3
  reactor.callLater(2, deferred.callback, inputData * 3)
  return deferred

def cbPrintData(result):
  """
  Data handling function to be added as a callback: handles the
  data by printing the result
  """
  print('Result received: {}'.format(result))

deferred = getDummyData(3)
deferred.addCallback(cbPrintData)

# manually set up the end of the process by asking the reactor to
# stop itself in 4 seconds time
reactor.callLater(4, reactor.stop)
# start up the Twisted reactor (event loop handler) manually
print('Starting the reactor')
reactor.run()

多個(gè)回調(diào)函數(shù)
在一個(gè)Deferred對(duì)象上可以關(guān)聯(lián)多個(gè)回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)鏈上的第一個(gè)回調(diào)函數(shù)會(huì)以Deferred對(duì)象的結(jié)果為參數(shù)來調(diào)用,而第二個(gè)回調(diào)函數(shù)以第一個(gè)函數(shù)的結(jié)果為參數(shù)來調(diào)用,依此類推。為什么需要這樣的機(jī)制呢?考慮一下這樣的情況,twisted.enterprise.adbapi返回一個(gè)Deferred對(duì)象——一個(gè)一個(gè)SQL查詢的結(jié)果,可能有某個(gè)web窗口會(huì)在這個(gè)Deferred對(duì)象上添加一個(gè)回調(diào)函數(shù),以把查詢結(jié)果轉(zhuǎn)換成HTML的格式,然后把Deferred對(duì)象繼續(xù)向前傳遞,這時(shí)Twisted會(huì)調(diào)用這個(gè)回調(diào)函數(shù)并把結(jié)果返回給HTTP客戶端。在出現(xiàn)錯(cuò)誤或者異常的情況下,回調(diào)函數(shù)鏈不會(huì)被調(diào)用。

from twisted.internet import reactor, defer


class Getter:
  def gotResults(self, x):
    """
    The Deferred mechanism provides a mechanism to signal error
    conditions. In this case, odd numbers are bad.

    This function demonstrates a more complex way of starting
    the callback chain by checking for expected results and
    choosing whether to fire the callback or errback chain
    """
    if self.d is None:
      print("Nowhere to put results")
      return

    d = self.d
    self.d = None
    if x % 2 == 0:
      d.callback(x * 3)
    else:
      d.errback(ValueError("You used an odd number!"))

  def _toHTML(self, r):
    """
    This function converts r to HTML.

    It is added to the callback chain by getDummyData in
    order to demonstrate how a callback passes its own result
    to the next callback
    """
    return "Result: %s" % r

  def getDummyData(self, x):
    """
    The Deferred mechanism allows for chained callbacks.
    In this example, the output of gotResults is first
    passed through _toHTML on its way to printData.

    Again this function is a dummy, simulating a delayed result
    using callLater, rather than using a real asynchronous
    setup.
    """
    self.d = defer.Deferred()
    # simulate a delayed result by asking the reactor to schedule
    # gotResults in 2 seconds time
    reactor.callLater(2, self.gotResults, x)
    self.d.addCallback(self._toHTML)
    return self.d


def cbPrintData(result):
  print(result)


def ebPrintError(failure):
  import sys
  sys.stderr.write(str(failure))


# this series of callbacks and errbacks will print an error message
g = Getter()
d = g.getDummyData(3)
d.addCallback(cbPrintData)
d.addErrback(ebPrintError)

# this series of callbacks and errbacks will print "Result: 12"
g = Getter()
d = g.getDummyData(4)
d.addCallback(cbPrintData)
d.addErrback(ebPrintError)

reactor.callLater(4, reactor.stop)
reactor.run()

需要注意的一點(diǎn)是,在方法gotResults中處理self.d的方式。在Deferred對(duì)象被結(jié)果或者錯(cuò)誤激活之前,這個(gè)屬性被設(shè)置成了None,這樣Getter實(shí)例就不會(huì)再持有將要激活的Deferred對(duì)象的引用。這樣做有幾個(gè)好處,首先,這樣可以避免Getter.gotResults有時(shí)會(huì)重復(fù)激活相同的Deferred對(duì)象的可能性(這樣會(huì)導(dǎo)致出現(xiàn)AlreadyCalledError異常)。其次,這樣做可以使得該Deferred對(duì)象上可以添加一個(gè)調(diào)用了Getter.getDummyData函數(shù)的回調(diào)函數(shù),而不會(huì)產(chǎn)生問題。還有,這樣使得Python垃圾收集器更容易通過引用循環(huán)來檢測(cè)出一個(gè)對(duì)象是否需要回收。
可視化的解釋
這里寫圖片描述

2016525111342144.jpg (503×187)
1.請(qǐng)求方法請(qǐng)求數(shù)據(jù)到Data Sink,得到返回的Deferred對(duì)象。
2.請(qǐng)求方法把回調(diào)函數(shù)關(guān)聯(lián)到Deferred對(duì)象上。

2016525111420537.jpg (240×382)

1.當(dāng)結(jié)果已經(jīng)準(zhǔn)備好后,把它傳遞給Deferred對(duì)象。如果操作成功就調(diào)用Deferred對(duì)象的.callback(result)方法,如果操作失敗就調(diào)用Deferred對(duì)象的.errback(faliure)方法。注意failure是twisted.python.failure.Failure類的一個(gè)實(shí)例。
2.Deferred對(duì)象使用result或者faliure來激活之前添加的回調(diào)函數(shù)或者錯(cuò)誤處理回調(diào)函數(shù)。然后就按照下面的規(guī)則來沿著回調(diào)函數(shù)鏈繼續(xù)執(zhí)行下去:
回調(diào)函數(shù)的結(jié)果總是當(dāng)做第一個(gè)參數(shù)被傳遞給下一個(gè)回調(diào)函數(shù),這樣就形成了一個(gè)鏈?zhǔn)降奶幚砥鳌?br /> 如果一個(gè)回調(diào)函數(shù)拋出了異常,就轉(zhuǎn)到錯(cuò)誤處理回調(diào)函數(shù)來執(zhí)行。
如果一個(gè)faliure沒有得到處理,那么它會(huì)沿著錯(cuò)誤處理回調(diào)函數(shù)鏈一直傳遞下去,這有點(diǎn)像異步版本的except語(yǔ)句。
如果一個(gè)錯(cuò)誤處理回調(diào)函數(shù)沒有拋出異常或者返回一個(gè)twisted.python.failure.Failure實(shí)例,那么接下來就轉(zhuǎn)到去執(zhí)行回調(diào)函數(shù)。
錯(cuò)誤處理回調(diào)函數(shù)
Deferred對(duì)象的錯(cuò)誤處理模型是以Python的異常處理為基礎(chǔ)的。在沒有錯(cuò)誤發(fā)生的情況下,所有的回調(diào)函數(shù)都會(huì)被執(zhí)行,一個(gè)接著一個(gè),就像上面所說的那樣。
如果沒有執(zhí)行回調(diào)函數(shù)而是執(zhí)行了錯(cuò)誤處理回調(diào)函數(shù)(比如DB查詢發(fā)生了錯(cuò)誤),那么一個(gè)twisted.python.failure.Failure對(duì)象會(huì)被傳遞給第一個(gè)錯(cuò)誤處理回調(diào)函數(shù)(你可以添加多個(gè)錯(cuò)誤處理回調(diào)函數(shù),就像回調(diào)函數(shù)鏈一樣)。可以把錯(cuò)誤處理回調(diào)函數(shù)鏈當(dāng)做普通Python代碼中的except代碼塊。
除非在except代碼塊中顯式地raise了一個(gè)錯(cuò)誤,否則Exception對(duì)象就會(huì)被捕捉到且不再繼續(xù)傳播下去,然后又開始正常地執(zhí)行程序。對(duì)于錯(cuò)誤處理回調(diào)函數(shù)鏈來說也是一樣,除非你顯式地return一個(gè)Faliure或者重新拋出一個(gè)異常,否則錯(cuò)誤就會(huì)停止繼續(xù)傳播,然后就會(huì)從那里開始執(zhí)行正常的回調(diào)函數(shù)鏈(使用錯(cuò)誤處理回調(diào)函數(shù)返回的值)。如果錯(cuò)誤處理回調(diào)函數(shù)返回了一個(gè)Faliure或者拋出了一個(gè)異常,那么這個(gè)Faliure或者異常就會(huì)被傳遞給下一個(gè)錯(cuò)誤處理回調(diào)函數(shù)。
注意,如果一個(gè)錯(cuò)誤處理回調(diào)函數(shù)什么也沒有返回,那它實(shí)際上返回的是None,這就意味著在這個(gè)錯(cuò)誤處理回調(diào)函數(shù)執(zhí)行之后會(huì)繼續(xù)回調(diào)函數(shù)鏈的執(zhí)行。這可能不是你實(shí)際上期望的那樣,所以要確保你的錯(cuò)誤處理回調(diào)函數(shù)返回一個(gè)Faliure對(duì)象(或者就是傳遞給它當(dāng)參數(shù)的那個(gè)Faliure對(duì)象)或者一個(gè)有意義的返回值以便來執(zhí)行下一個(gè)回調(diào)函數(shù)。
twisted.python.failure.Failure有一個(gè)有用的方法叫做trap,可以讓下面的代碼變成更有效率的另一種形式:

try:
  # code that may throw an exception
  cookSpamAndEggs()
except (SpamException, EggException):
  # Handle SpamExceptions and EggExceptions
  ...

可以寫成:

def errorHandler(failure):
  failure.trap(SpamException, EggException)
  # Handle SpamExceptions and EggExceptions

d.addCallback(cookSpamAndEggs)
d.addErrback(errorHandler)

如果傳遞給faliure.trap的參數(shù)沒有能和Faliure中的錯(cuò)誤匹配的,那它會(huì)重新拋出這個(gè)錯(cuò)誤。
還有一個(gè)需要注意的地方,twisted.internet.defer.Deferred.addCallbacks方法的功能和addCallback再跟上addErrback的功能是類似的,但不完全一樣??紤]一下下面的情況:

# Case 1
d = getDeferredFromSomewhere()
d.addCallback(callback1)    # A
d.addErrback(errback1)     # B
d.addCallback(callback2)
d.addErrback(errback2)

# Case 2
d = getDeferredFromSomewhere()
d.addCallbacks(callback1, errback1) # C
d.addCallbacks(callback2, errback2)

對(duì)于Case 1來說,如果在callback1里面發(fā)生了錯(cuò)誤,那么errback1就會(huì)被調(diào)用。而對(duì)于Case 2來說,被調(diào)用的卻是是errback2。
實(shí)際上是因?yàn)?,在Case 1中,行A會(huì)處理getDeferredFromSomewhere執(zhí)行成功的情況,行B會(huì)處理發(fā)生在getDeferredFromSomewhere執(zhí)行時(shí)或者行A的callback1執(zhí)行時(shí)的錯(cuò)誤。而在Case 2中,行C中的errback1只會(huì)處理getDeferredFromSomewhere執(zhí)行時(shí)產(chǎn)生的錯(cuò)誤,而不會(huì)負(fù)責(zé)callback1中產(chǎn)生的錯(cuò)誤。
未處理的錯(cuò)誤
如果一個(gè)Deferred對(duì)象在還有一個(gè)未處理的錯(cuò)誤時(shí)(即如果它還有下一個(gè)errback就一定會(huì)調(diào)用)就被垃圾收集器清除掉了,那么Twisted會(huì)把這個(gè)錯(cuò)誤的traceback記錄到日志文件里。這意味著你可能不用添加errback仍然能夠記錄錯(cuò)誤。不過要小心的是,如果你還持有這個(gè)Deferred對(duì)象的引用,并且它永遠(yuǎn)不會(huì)被垃圾收集器清理,那么你就會(huì)永遠(yuǎn)看不到這個(gè)錯(cuò)誤(而且你的callbacks會(huì)神秘地永遠(yuǎn)不會(huì)執(zhí)行)。如果不確定上述情況是否會(huì)發(fā)生,你應(yīng)當(dāng)在回調(diào)函數(shù)鏈之后顯式地添加一個(gè)errback,即使只是這樣寫:

# Make sure errors get logged
from twisted.python import log
d.addErrback(log.err)

處理同步和異步結(jié)果
在一些應(yīng)用中,可能同時(shí)會(huì)有同步的函數(shù),也會(huì)有異步的函數(shù)。例如,對(duì)于一個(gè)用戶認(rèn)證函數(shù),如果它是從內(nèi)存中檢查用戶是否已經(jīng)認(rèn)證,這樣就可以立即返回結(jié)果;但是如果它需要等待網(wǎng)絡(luò)上的數(shù)據(jù),那它就應(yīng)當(dāng)返回一個(gè)當(dāng)數(shù)據(jù)到達(dá)時(shí)就激活的Deferred對(duì)象。這就是說,一個(gè)想要去檢查用戶是否已經(jīng)認(rèn)證的函數(shù)需要能同時(shí)接受立即返回的結(jié)果和Deferred對(duì)象。
下面的例子中,authenticateUser使用了isValidUser來認(rèn)證用戶:

def authenticateUser(isValidUser, user):
  if isValidUser(user):
    print("User is authenticated")
  else:
    print("User is not authenticated")

這個(gè)函數(shù)假定isValidUser是立即返回的,然而實(shí)際上isValidUser可能是異步認(rèn)證用戶的并且返回的是一個(gè)Deferred對(duì)象。把這個(gè)函數(shù)調(diào)整為既能接收同步的isValidUser又能接收異步的isValidUser是有可能的。同時(shí)把同步的函數(shù)改成返回值為Deferred對(duì)象也是可以的。
在庫(kù)函數(shù)代碼中處理可能的Deferred對(duì)象
這是一個(gè)可能被傳遞給authenticateUser的同步的用戶認(rèn)證方法:

def synchronousIsValidUser(user):
  '''
  Return true if user is a valid user, false otherwise
  '''
  return user in ["Alice", "Angus", "Agnes"]

這是一個(gè)異步的用戶認(rèn)證方法,返回一個(gè)Deferred對(duì)象:

from twisted.internet import reactor, defer

def asynchronousIsValidUser(user):
  d = defer.Deferred()
  reactor.callLater(2, d.callback, user in ["Alice", "Angus", "Agnes"])
  return d

我們最初對(duì)authenticateUser的實(shí)現(xiàn)希望isValidUser是同步的,但是現(xiàn)在需要把它改成既能處理同步又能處理異步的isValidUser實(shí)現(xiàn)。對(duì)此,可以使用maybeDeferred函數(shù)來調(diào)用isValidUser,這個(gè)函數(shù)可以保證isValidUser函數(shù)的返回值是一個(gè)Deferred對(duì)象,即使isValidUser是一個(gè)同步的函數(shù):

from twisted.internet import defer

def printResult(result):
  if result:
    print("User is authenticated")
  else:
    print("User is not authenticated")

def authenticateUser(isValidUser, user):
  d = defer.maybeDeferred(isValidUser, user)
  d.addCallback(printResult)

現(xiàn)在isValidUser無(wú)論是同步的還是異步的都可以了。
也可以把synchronousIsValidUser函數(shù)改寫成返回一個(gè)Deferred對(duì)象,可以參考這里。
取消回調(diào)函數(shù)
動(dòng)機(jī):一個(gè)Deferred對(duì)象可能需要很長(zhǎng)時(shí)間才會(huì)調(diào)用回調(diào)函數(shù),甚至于永遠(yuǎn)也不會(huì)調(diào)用。有時(shí)候可能沒有那么好的耐心來等待Deferred返回結(jié)果。既然Deferred完成后要執(zhí)行的所有代碼都在你的應(yīng)用中或者調(diào)用的庫(kù)中,那么你就可以選擇在已經(jīng)過去了很長(zhǎng)時(shí)間才收到結(jié)果時(shí)忽略這個(gè)結(jié)果。然而,即使你選擇忽略這個(gè)結(jié)果,這個(gè)Deferred對(duì)象產(chǎn)生的底層操作仍然在后臺(tái)工作著,并且消耗著機(jī)器資源,比如CPU時(shí)間、內(nèi)存、網(wǎng)絡(luò)帶寬甚至磁盤容量。因此,當(dāng)用戶關(guān)閉窗口,點(diǎn)擊了取消按鈕,從你的服務(wù)器上斷開連接或者發(fā)送了一個(gè)“停止”的指令,這時(shí)你需要顯式地聲明你對(duì)之前原定的操作的結(jié)果已經(jīng)不再感興趣了,以便原先的Deferred對(duì)象可以做一些清理的工作并釋放資源。
這是一個(gè)簡(jiǎn)單的例子,你想連接到一個(gè)外部的機(jī)器,但是這個(gè)機(jī)器太慢了,所以需要在應(yīng)用中添加一個(gè)取消按鈕來終止這次連接企圖,以便用戶可以連接到另一個(gè)機(jī)器。這里是這樣的一個(gè)應(yīng)用的大概邏輯:

def startConnecting(someEndpoint):
  def connected(it):
    "Do something useful when connected."
  return someEndpoint.connect(myFactory).addCallback(connected)
# ...
connectionAttempt = startConnecting(endpoint)
def cancelClicked():
  connectionAttempt.cancel()

顯然,startConnecting被一些UI元素用來讓用戶選擇連接哪個(gè)機(jī)器。然后是一個(gè)取消按鈕陪著到cancelClicked函數(shù)上。
當(dāng)connectionAttempt.cancel被調(diào)用時(shí),會(huì)發(fā)生以下操作:

  • 導(dǎo)致潛在的連接操作終止,如果它仍然在進(jìn)行中的話
  • 不管怎樣,使得connectionAttempt這個(gè)Deferred對(duì)象及時(shí)地被完成
  • 有可能導(dǎo)致connectionAttempt這個(gè)Deferred對(duì)象因?yàn)镃ancelledError錯(cuò)誤調(diào)用錯(cuò)誤處理函數(shù)

即使這個(gè)取消操作已經(jīng)表達(dá)了讓底層的操作停止的需求,但是底層的操作不大可能馬上就對(duì)此作出反應(yīng)。甚至在這個(gè)簡(jiǎn)單的例子中就有一個(gè)不會(huì)被中斷的操作:域名解析,因此需要在在一個(gè)線程中執(zhí)行;這個(gè)應(yīng)用中的連接操作如果在等待域名解析的時(shí)候就不能被取消。所以你要取消的Deferred對(duì)象可能不會(huì)立即調(diào)用回調(diào)函數(shù)或錯(cuò)誤處理回調(diào)函數(shù)。

一個(gè)Deferred對(duì)象可能會(huì)在執(zhí)行它的回調(diào)函數(shù)鏈的任何一點(diǎn)時(shí)等待另一個(gè)Deferred對(duì)象的完成。沒有方法可以在回調(diào)函數(shù)鏈的特定一個(gè)點(diǎn)知道是否每件事都已經(jīng)準(zhǔn)備好了。由于有可能一個(gè)回調(diào)函數(shù)鏈的很多層次上的函數(shù)都會(huì)希望取消同一個(gè)Deferred對(duì)象,那么鏈上任何層次的函數(shù)在任意時(shí)刻都有可能調(diào)用.cancel()函數(shù)。.cancel()函數(shù)從不拋出任何異常或者返回任何值。你可以重復(fù)調(diào)用它,即使這個(gè)Deferred對(duì)象已經(jīng)被激活了,它已經(jīng)沒有剩余的回調(diào)函數(shù)了。
在實(shí)例化了一個(gè)Deferred對(duì)象的同時(shí),可以給它提供一個(gè)取消函數(shù)(Deferred對(duì)象的構(gòu)造函數(shù)為def __init__(self, canceller=None): (source)),這個(gè)canceller可以做任何事情。理想情況下,它做的每件事情都都會(huì)阻止之前你請(qǐng)求的操作,但是并不總是能保證這樣。所以Deferred對(duì)象的取消只是盡力而為。原因有幾點(diǎn):
Deferred對(duì)象不知道怎樣取消底層的操作。
底層的操作已經(jīng)執(zhí)行到了一個(gè)不可取消的狀態(tài),因?yàn)榭赡芤呀?jīng)執(zhí)行了一些不可逆的操作。
Deferred對(duì)象可能已經(jīng)有了結(jié)果,所以沒有要取消的東西了。
調(diào)用cancel()函數(shù)后,不管是否能取消,總會(huì)得到成功的結(jié)果,不會(huì)出現(xiàn)出錯(cuò)的情況。在第一種和第二和情況下,由于底層的操作仍在繼續(xù),Deferred對(duì)象大可以twisted.internet.defer.CancelledError為參數(shù)來調(diào)用它的errback。
如果取消的Deferred對(duì)象正在等待另一個(gè)Deferred對(duì)象,那么取消操作會(huì)向前傳遞到此Deferred對(duì)象。
可以參考API。
默認(rèn)的取消行為
所有的Deferred對(duì)象都支持取消,但是只提供了很簡(jiǎn)單的行為,也沒有釋放任何資源。
考慮一下下面的例子:

operation = Deferred()
def x(result):
  print("Hooray, a result:" + repr(x))
operation.addCallback(x)
# ...
def operationDone():
  operation.callback("completed")

如果需要取消operation這個(gè)Deferred對(duì)象,而operation沒有一個(gè)canceller的取消函數(shù),就會(huì)產(chǎn)生下面兩種之一的結(jié)果:
如果operationDone已經(jīng)被調(diào)用了,也就是operation對(duì)象已經(jīng)完成了,那么什么都不會(huì)改變。operation仍然有一個(gè)結(jié)果,不過既然沒有其他的回調(diào)函數(shù)了,所以沒有什么行為上可以看到的變化。
如果operationDone已經(jīng)還沒有被調(diào)用,那么operation會(huì)馬上以CancelledError為參數(shù)激活errback。
在正常情況下,如果一個(gè)Deferred對(duì)象已經(jīng)調(diào)用了回調(diào)函數(shù)再來調(diào)用callback會(huì)導(dǎo)致一個(gè)AlreadyCalledError。因此,callback可以在已經(jīng)取消的、但是沒有canceller的Deferred對(duì)象上再調(diào)用一次,只會(huì)導(dǎo)致一個(gè)空操作。如果你多次調(diào)用callback,仍會(huì)得到一個(gè)AlreadyCalledError異常。
創(chuàng)建能取消的Deferred對(duì)象:自定義取消函數(shù)
假設(shè)你在實(shí)現(xiàn)一個(gè)HTTP客戶端,返回一個(gè)在服務(wù)器返回響應(yīng)的時(shí)候會(huì)激活的Deferred對(duì)象。取消最好是關(guān)閉連接。為了讓取消函數(shù)這么做,可以向Deferred對(duì)象的構(gòu)造函數(shù)中傳遞一個(gè)函數(shù)作為參數(shù)(當(dāng)Deferred對(duì)象被取消的時(shí)候會(huì)調(diào)用這個(gè)函數(shù)):

class HTTPClient(Protocol):
  def request(self, method, path):
    self.resultDeferred = Deferred(
      lambda ignore: self.transport.abortConnection())
    request = b"%s %s HTTP/1.0\r\n\r\n" % (method, path)
    self.transport.write(request)
    return self.resultDeferred

  def dataReceived(self, data):
    # ... parse HTTP response ...
    # ... eventually call self.resultDeferred.callback() ...

現(xiàn)在如果在HTTPClient.request()返回的Deferred對(duì)象上調(diào)用了cancel()函數(shù),這個(gè)HTTP請(qǐng)求就會(huì)取消(如果沒有太晚的話)。要注意的是,還要在一個(gè)已經(jīng)被取消的、帶有canceller的Deferred對(duì)象上調(diào)用callback()。
DeferredList
有時(shí)你想在幾個(gè)不同的事件都發(fā)生后再得到通知,而不是每個(gè)事件發(fā)生都會(huì)通知一下。例如,你想等待一個(gè)列表中所有的連接都關(guān)閉。twisted.internet.defer.DeferredList就適用于這種情況。
用多個(gè)Deferred對(duì)象來創(chuàng)建一個(gè)DeferredList,只需傳遞一個(gè)你想等待的Deferred對(duì)象的列表即可:
# Creates a DeferredList
dl = defer.DeferredList([deferred1, deferred2, deferred3])

現(xiàn)在就可以把這個(gè)DeferredList當(dāng)成一個(gè)普通的Deferred來看待了,例如你也可以調(diào)用addCallbacks等等。這個(gè)DeferredList會(huì)在所有的Deferred對(duì)象都完成之后才調(diào)用它的回調(diào)函數(shù)。這個(gè)回調(diào)函數(shù)的參數(shù)是這個(gè)DeferredList對(duì)象中包含的所有Deferred對(duì)象返回結(jié)果的列表,例如:

# A callback that unpacks and prints the results of a DeferredList
def printResult(result):
  for (success, value) in result:
    if success:
      print('Success:', value)
    else:
      print('Failure:', value.getErrorMessage())

# Create three deferreds.
deferred1 = defer.Deferred()
deferred2 = defer.Deferred()
deferred3 = defer.Deferred()

# Pack them into a DeferredList
dl = defer.DeferredList([deferred1, deferred2, deferred3], consumeErrors=True)

# Add our callback
dl.addCallback(printResult)

# Fire our three deferreds with various values.
deferred1.callback('one')
deferred2.errback(Exception('bang!'))
deferred3.callback('three')

# At this point, dl will fire its callback, printing:
#  Success: one
#  Failure: bang!
#  Success: three
# (note that defer.SUCCESS == True, and defer.FAILURE == False)

正常情況下DeferredList不會(huì)調(diào)用errback,但是除非把cousumeErrors設(shè)置成True,否則在Deferred對(duì)象中產(chǎn)生的錯(cuò)誤仍然會(huì)激活每個(gè)Deferred對(duì)象各自的errback。
注意,如果你想在添加到DeferredList中去的Deferred對(duì)象上應(yīng)用回調(diào)函數(shù),那么就需要注意添加回調(diào)函數(shù)的時(shí)機(jī)。把一個(gè)Deferred對(duì)象添加到DeferredList中會(huì)導(dǎo)致同時(shí)也給該Deferred對(duì)象添加了一個(gè)回調(diào)函數(shù)(當(dāng)這個(gè)回調(diào)函數(shù)運(yùn)行的時(shí)候,它的功能是檢查DeferredList是否已經(jīng)完成了)。最重要的是,變量這個(gè)回調(diào)函數(shù)把記錄了Deferred對(duì)象的返回值并把這個(gè)值傳遞到最終交給DeferredList回調(diào)函數(shù)當(dāng)做參數(shù)的列表中。
因此,如果你在把一個(gè)Deferred添加到DeferredList之后又給這個(gè)Deferred對(duì)象添加了一個(gè)回調(diào)函數(shù),那么這個(gè)新添加的回調(diào)函數(shù)的返回值不會(huì)被傳遞到DeferredList的回調(diào)函數(shù)中。為了避免這種情況的發(fā)生,建議不要在把一個(gè)Deferred對(duì)象添加到DeferredList中之后再給這個(gè)Deferred添加回調(diào)函數(shù)。

def printResult(result):
  print(result)

def addTen(result):
  return result + " ten"

# Deferred gets callback before DeferredList is created
deferred1 = defer.Deferred()
deferred2 = defer.Deferred()
deferred1.addCallback(addTen)
dl = defer.DeferredList([deferred1, deferred2])
dl.addCallback(printResult)
deferred1.callback("one") # fires addTen, checks DeferredList, stores "one ten"
deferred2.callback("two")
# At this point, dl will fire its callback, printing:
#   [(True, 'one ten'), (True, 'two')]

# Deferred gets callback after DeferredList is created
deferred1 = defer.Deferred()
deferred2 = defer.Deferred()
dl = defer.DeferredList([deferred1, deferred2])
deferred1.addCallback(addTen) # will run *after* DeferredList gets its value
dl.addCallback(printResult)
deferred1.callback("one") # checks DeferredList, stores "one", fires addTen
deferred2.callback("two")
# At this point, dl will fire its callback, printing:
#   [(True, 'one), (True, 'two')]

DeferredList接受三個(gè)關(guān)鍵字參數(shù)來定制它的行為:fireOnOneCallback、fireOnOneErrback和cousumeErrors。如果設(shè)置了fireOnOneCallback,那么只要有一個(gè)Deferred對(duì)象調(diào)用了它的回調(diào)函數(shù),DeferredList就會(huì)立即調(diào)用它的回調(diào)函數(shù)。相似的,如果設(shè)置了fireOnOneErrback,那么只要有一個(gè)Deferred調(diào)用了errback,DeferredList就會(huì)調(diào)用它的errback。注意,DeferredList只是一次性的,所以在一次callback或者errback調(diào)用之后,它就會(huì)什么也不做(它會(huì)忽略它的Deferred傳遞給它的所有結(jié)果)。
fireOnOneErrback選項(xiàng)在你想等待所有事情成功執(zhí)行,而且需要在出錯(cuò)時(shí)馬上知道的情形下是很有用的。
consumeErrors參數(shù)會(huì)使DeferredList中包含的Deferred對(duì)象中產(chǎn)生的錯(cuò)誤在建立了DeferredList對(duì)象之后,不會(huì)傳遞給原來每個(gè)Deferred對(duì)象各自的errbacks。創(chuàng)建了DeferredList對(duì)象之后,任何單個(gè)Deferred對(duì)象中產(chǎn)生的錯(cuò)誤會(huì)被轉(zhuǎn)化成結(jié)果為None的回調(diào)調(diào)用。用這個(gè)選項(xiàng)可以防止它所包含的Deferred中的“Unhandled error in Deferred”警告,而不用添加額外的errbacks(否則要消除這個(gè)警告就需要為每個(gè)Deferred對(duì)象添加errback)。 給consumeErrors參數(shù)傳遞一個(gè)True不會(huì)影響fireOnOneCallback和fireOnOneErrback的行為。應(yīng)該總是使用這個(gè)參數(shù),除非你想在將來給這些列表中的Deferred對(duì)象添加callbacks或errbacks,或者除非你知道它們不會(huì)產(chǎn)生錯(cuò)誤。否則,產(chǎn)生錯(cuò)誤的話會(huì)導(dǎo)致一個(gè)被Twisted記錄到日志中的“unhandled error”。
DeferredList一個(gè)普遍的用法是把一些并行的異步操作結(jié)果組合到一起。如果所有的操作都成功了,那就可以操作成功,如果有一個(gè)操作失敗了,那么就操作失敗。twisted.internet.defer.gatherResults是一個(gè)快捷方式:

from twisted.internet import defer
d1 = defer.Deferred()
d2 = defer.Deferred()
d = defer.gatherResults([d1, d2], consumeErrors=True)

def cbPrintResult(result):
  print(result)

d.addCallback(cbPrintResult)

d1.callback("one")
# nothing is printed yet; d is still awaiting completion of d2
d2.callback("two")
# printResult prints ["one", "two"]

鏈?zhǔn)降腄eferred
如果你需要一個(gè)Deferred對(duì)象來等待另一個(gè)Deferred對(duì)象的執(zhí)行,你所要做的只是從它的回調(diào)函數(shù)鏈中的回調(diào)函數(shù)中返回一個(gè)Deferred對(duì)象。具體點(diǎn),如果你從某個(gè)Deferred對(duì)象A的一個(gè)回調(diào)函數(shù)中返回Deferred對(duì)象B,那么A的回調(diào)函數(shù)鏈就會(huì)在B的callback()函數(shù)調(diào)用之前進(jìn)行等待。此時(shí),A的下一個(gè)回調(diào)函數(shù)的第一個(gè)參數(shù)就是B的最后一個(gè)回調(diào)函數(shù)返回的結(jié)果。
注意,如果一個(gè)`Deferred`對(duì)象在它的回調(diào)函數(shù)中直接或者間接地返回了它本身,那么這樣的行為是沒有定義的。代碼會(huì)試圖檢測(cè)出這種情況然后給出警告。在將來可能會(huì)直接拋出異常。
如果這看起來有點(diǎn)復(fù)雜,也不要擔(dān)心——當(dāng)你遇到這種情況的時(shí)候,你可能會(huì)直接認(rèn)出來并且知道為什么會(huì)產(chǎn)生這樣的結(jié)果。如果你需要手動(dòng)地把Deferred對(duì)象
鏈接起來,這里有一個(gè)方便的方法:

chainDeferred(otherDeferred)

總結(jié)
我們認(rèn)識(shí)到了deferred是如何幫我們解決這些問題的:
我們不能忽視errback,在任何異步編程的API中都需要它。Deferred支持errbacks。
激活回調(diào)多次可能會(huì)導(dǎo)致很嚴(yán)重的問題。Deferred只能被激活一次,這就類似于同步編程中的try/except的處理方法。
含有回調(diào)的程序在重構(gòu)時(shí)相當(dāng)困難。有了deferred,我們就通過修改回調(diào)鏈來重構(gòu)程序。

相關(guān)文章

  • 深入了解Python?Opencv數(shù)據(jù)增強(qiáng)

    深入了解Python?Opencv數(shù)據(jù)增強(qiáng)

    常見的數(shù)據(jù)增強(qiáng)操作有:按比例放大或縮小圖片、旋轉(zhuǎn)、平移、水平翻轉(zhuǎn)、改變圖像通道等。本文將通過Python?OpenCV實(shí)現(xiàn)這些操作,需要的可以參考一下
    2022-02-02
  • Linux下編譯安裝MySQL-Python教程

    Linux下編譯安裝MySQL-Python教程

    這篇文章主要介紹了Linux下編譯安裝MySQL-Python教程,本文使用編譯方式安裝,提供下載地址和測(cè)試安裝成功方法,需要的朋友可以參考下
    2015-02-02
  • Python輸出列表(list)的倒序/逆序的幾種方法

    Python輸出列表(list)的倒序/逆序的幾種方法

    列表是一個(gè)有序的元素集合,而列表的倒序或逆序操作也是常見的需求之一,本文主要介紹了Python輸出列表(list)的倒序/逆序的幾種方法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02
  • python畫圖常規(guī)設(shè)置方式

    python畫圖常規(guī)設(shè)置方式

    這篇文章主要介紹了python畫圖常規(guī)設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-03-03
  • python函數(shù)遞歸調(diào)用的實(shí)現(xiàn)

    python函數(shù)遞歸調(diào)用的實(shí)現(xiàn)

    本文主要介紹了python函數(shù)遞歸調(diào)用的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • python ddt實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)

    python ddt實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)

    這篇文章主要為大家詳細(xì)介紹了python ddt實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • pandas如何靈活增加新的空字段

    pandas如何靈活增加新的空字段

    這篇文章主要介紹了pandas如何靈活增加新的空字段問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • 解決Python中定時(shí)任務(wù)線程無(wú)法自動(dòng)退出的問題

    解決Python中定時(shí)任務(wù)線程無(wú)法自動(dòng)退出的問題

    今天小編就為大家分享一篇解決Python中定時(shí)任務(wù)線程無(wú)法自動(dòng)退出的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2019-02-02
  • Numpy 多維數(shù)據(jù)數(shù)組的實(shí)現(xiàn)

    Numpy 多維數(shù)據(jù)數(shù)組的實(shí)現(xiàn)

    這篇文章主要介紹了Numpy 多維數(shù)據(jù)數(shù)組的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • pytest生成簡(jiǎn)單自定義測(cè)試結(jié)果的html報(bào)告

    pytest生成簡(jiǎn)單自定義測(cè)試結(jié)果的html報(bào)告

    這篇文章主要為大家介紹了pytest生成簡(jiǎn)單自定義測(cè)試結(jié)果html報(bào)告,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06

最新評(píng)論