廣告
| Twisted的Callback機制 : Deferred |
|
|
|
| 作者是 Victor | |||
| 週三, 11 二月 2009 21:10 | |||
什麼是Deferred簡單的一句話來說,它是Twisted的Callback機制,或許會令人覺得很奇怪,Callback不就是函數當參數傳遞,執行完成後再呼叫如此而以,何以需要一個特別的機制,事實上除此之外還有更多事情要處理,而它的Deferred帶來很大的好處,我們直接參考 官方文件,來說明什麼是Deferred Deferred的使用方式
以下這張圖說明Deferred物件是這樣被使用的
這樣或許有些難懂,我們用一個實際一點的例子來說明
from twisted.internet import reactor
from twisted.web.client import getPage
def printPage(html):
print html
def printError(error):
print error
# 1. request google page
# 2. return deferred object
deferred = getPage('http://www.google.com')
# 3. add callbacks, printPage for print page content
# print error for print error message
deferred.addCallbacks(printPage, printError)
reactor.run()
這個程式會下載Google的首頁並且印出來,我們的程式兩個Callbacks函數就是圖形中的Data sink,也就是最後資料的去處,而Data source就是getPage函數,所以對應的步驟是
Deferred的運作方式以下這張圖是當非同步事件完成時,Deferred物件會呼叫Callback的流程 ![]()
為什麼要有這樣的機制?這樣的機制雖然沒有太複雜,但是也是要花一點心思去了解,那為何要有這樣的機制? 因為在這樣非同步的情況下,沒有地方去攔截例外,因為所有事件的處理都是reactor.run()在做,例外會從那裡跑出來,但是那並不是我們想要的,我們只想針對某個非同步的事件做處理,因此有了這套機制我們就可以針對我們想要的例外或失敗做處理,除此之外,它本身也是責任鏈的設計樣式(Design pattern),這表示你可以增加一串的函數來處理資料,舉個例子 取得網頁資料 -> 解析網頁資料 -> 計算統計分析 -> 儲存到資料庫 你可以有這樣一串的責任各負責不同的事情,它們都不與前後要做的事情無關,所以重覆使用性就大大提升,而且就算其中一個環節出錯,它並不是丟出例外,而是向下傳播,如果你在某一層上不想處理那樣的錯誤,就不要加上errback,錯誤還是會往下傳播,而且它們又無知於上層和下層,達到降低藕合度的效果。 Deferred使用的技巧 Deferred在回傳的時候,就是增加callback的好時機,舉個例子你可以這樣封裝你非同步函數
import re
from twisted.internet import reactor
from twisted.web.client import getPage
def parsePage(html):
result = re.match('(.*)<title>(.*?)</title>(.*)', html, re.IGNORECASE)
return result.groups()[1]
def getTitle(url):
d = getPage(url)
d.addCallback(parsePage)
return d
def printTitle(title):
print title
def printError(error):
print error
# get page's title
deferred = getTitle('http://www.google.com')
deferred.addCallbacks(printTitle, printError)
reactor.run()
在這個例子中我們封裝了getPage,取得它回傳的Deferred然後增加上我們的parsePage callback,如此一來外界看來這個getTitle就是取得某個網頁然後解析title的行為,當然,你也可以選擇不封裝成getTitle,而只是使用parsePage當做第一個callback去處理html
import re
from twisted.internet import reactor
from twisted.web.client import getPage
def parsePage(html):
result = re.match('(.*)<title>(.*?)</title>(.*)', html, re.IGNORECASE)
return result.groups()[1]
def printTitle(title):
print title
def printError(error):
print error
# get page's title
deferred = getPage('http://www.google.com')
deferred.addCallbacks(parsePage, printError)
deferred.addCallbacks(printTitle, printError)
reactor.run()
你有你的選擇,這就是它的彈性 Callback的回呼參數callback函數的第一個參數是回傳的資料,在讀完上面的說明相信都已經很清楚,但是事實上很多時候,我們會有很多非同步事件同時進行,而完成時光靠第一個參數的資料,我們並不能知道當初我們呼叫這函數的目的為何,所以很多時候我們都想要在事情完成時,順便附帶一些當初這任務執行時的訊息,這時候就可以用到回呼參數了,下面這個例子示範如何使用回呼參數
from twisted.internet import reactor
from twisted.web.client import getPage
def printPage(html, siteName):
print siteName, 'page length', len(html)
def printError(error):
print error
deferred = getPage('http://www.google.com')
deferred.addCallbacks(printPage, printError, callbackArgs=('Google',))
deferred = getPage('http://yahoo.com')
deferred.addCallbacks(printPage, printError, callbackArgs=('Yahoo',))
reactor.run()
回呼參數會當做第二個參數傳給callback,如此一來就可以夾代一些有用的資訊給回呼函數去處理,errback一樣可以使用這樣的參數,關於詳細情況請查詢Twisted的API參考手冊 為什麼我的錯誤不會被印出來?有個很常見的問題就是,如果你的某個callback函數丟出某個例外,可是你卻發現錯誤訊息沒有被補捉並且印出來,以下例子示範這樣的情況
from twisted.internet import reactor
from twisted.web.client import getPage
def printPage(html):
# raise a exception
a = 10 / 0
print html
def printError(error):
print error
deferred = getPage('http://www.google.com')
deferred.addCallbacks(printPage, printError)
reactor.run()
你會發現你一直等,但是就是不見錯誤訊息出現,或是結果印出,原因出在於,未被補捉的錯誤,會在deferred物件被摧毀時才會印出,所以你看不到它被印出來,解決的方法就是,在下面再加一層的errback,如此一來就能補捉到上一層的錯誤,不用等到deferred被摧毀
from twisted.internet import reactor
from twisted.web.client import getPage
def printPage(html):
# raise a exception
a = 10 / 0
print html
def printError(error):
print error
deferred = getPage('http://www.google.com')
deferred.addCallbacks(printPage, printError)
deferred.addErrback(printError)
reactor.run()
確保callback都有回傳資料以上範例都因為目的只為印出資料程式便結束了,如果說你的程式想要被重覆使用,可以和其它callback串在一起,最好在每個callback後面都回傳資料,否則Python預設沒回傳的回傳值為None,下面的callback就不能處理上面的資料,以下範例示範正確的做法
from twisted.internet import reactor
from twisted.web.client import getPage
def printPage(html):
print html
# we pass it to chain, so other callback can handle it
return html
def printError(error):
print error
# pass it
return error
deferred = getPage('http://www.google.com')
deferred.addCallbacks(printPage, printError)
reactor.run()
請注意,如果errback沒有回傳值的話,依照Deferred的規則,回傳值不是失敗或是沒有丟出例外的話,會轉到callback繼續執行
|
核心是 Joomla!. Designed by: Free Joomla Theme, whois protect. Valid XHTML and CSS.




