文章目錄
- 1,起源
- 2,讓代碼變得優美一點
- 3,讓代碼再優美一點
- 4,加上參數
- 5,裝飾有傳回值的函數
- 6,應用多個裝飾器
- 7,靈活運用
想理解Python的decorator首先要知道在Python中函數也是一個對象,所以你可以
函數在Python中給變數的用法一樣也是一等公民,也就是高階函數(High Order Function)。所有的魔法都是由此而來。
1,起源
我們想在函數login中輸出調試資訊,我們可以這樣做
def login():print('in login')def printdebug(func):print('enter the login')func()print('exit the login')printdebug(login)
這個方法討厭的是每次調用login是,都通過printdebug來調用,但畢竟這是可行的。
2,讓代碼變得優美一點
既然函數可以作為傳回值,可以賦值給變數,我們可以讓代碼優美一點。
def login():print('in login')def printdebug(func):def __decorator():print('enter the login')func()print('exit the login')return __decorator #function as return valuedebug_login = printdebug(login) #function assign to variabledebug_login() #execute the returned function
這樣我們每次只要調用debug_login就可以了,這個名字更符合直覺。我們將原先的兩個函數printdebug和login綁定到一起,成為debug_login。這種耦合叫內聚:-)。
3,讓代碼再優美一點
printdebug和login是通過debug_login = printdebug(login)這一句來結合的,這一句似乎也是多餘的,能不能在定義login是加個標註,從而將printdebug和login結合起來?
上面的代碼從語句組織的角度來講很難再優美了,Python的解決方案是提供一個文法糖(Syntax Sugar),用一個@符號來結合它們。
def printdebug(func):def __decorator():print('enter the login')func()print('exit the login')return __decorator @printdebug #combine the printdebug and logindef login():print('in login')login() #make the calling point more intuitive
可以看出decorator就是一個:使用函數作參數並且返回函數的函數。通過改進我們可以得到:
- 更簡短的代碼,將結合點放在函數定義時
- 不改變原函數的函數名
在Python解譯器發現login調用時,他會將login轉換為printdebug(login)()。也就是說真正執行的是__decorator這個函數。
4,加上參數
1,login函數帶參數
login函數可能有參數,比如login的時候傳人user的資訊。也就是說,我們要這樣調用login:
login(user)
Python會將login的參數直接傳給__decorator這個函數。我們可以直接在__decorator中使用user變數:
def printdebug(func):def __decorator(user): #add parameter receive the user informationprint('enter the login')func(user) #pass user to loginprint('exit the login')return __decorator @printdebug def login(user):print('in login:' + user)login('jatsz') #arguments:jatsz
我們來解釋一下login(‘jatsz’)的調用過程:
[decorated] login(‘jatsz’) => printdebug(login)(‘jatsz’) => __decorator(‘jatsz’) => [real] login(‘jatsz’)
2,裝飾器本身有參數
我們在定義decorator時,也可以帶入參數,比如我們這樣使用decorator,我們傳入一個參數來指定debug level。
@printdebug(level=5)def login pass
為了給接收decorator傳來的參數,我們在原本的decorator上在封裝一個函數來接收參數:
def printdebug_level(level): #add wrapper to recevie decorator's parameterdef printdebug(func):def __decorator(user): print('enter the login, and debug level is: ' + str(level)) #print debug levelfunc(user) print('exit the login')return __decorator return printdebug #return original decorator@printdebug_level(level=5) #decorator's parameter, debug level set to 5def login(user):print('in login:' + user)login('jatsz')
我們再來解釋一下login(‘jatsz’)整個調用過程:
[decorated]login(‘jatsz’) => printdebug_level(5) => printdebug[with closure value 5](login)(‘jatsz’) => __decorator(‘jatsz’)[use value 5] => [real]login(‘jatsz’)
5,裝飾有傳回值的函數
有時候login會有傳回值,比如返回message來表明login是否成功。
login_result = login(‘jatsz’)
我們需要將傳回值在decorator和調用函數間傳遞:
def printdebug(func):def __decorator(user): print('enter the login')result = func(user) #recevie the native function call resultprint('exit the login')return result #return to callerreturn __decorator @printdebug def login(user):print('in login:' + user)msg = "success" if user == "jatsz" else "fail"return msg #login with a return valueresult1 = login('jatsz');print result1 #print login resultresult2 = login('candy');print result2
我們解釋一下傳回值的傳遞過程:
...omit for brief…[real][msg from login(‘jatsz’) => [result from]__decorator => [assign to] result1
6,應用多個裝飾器
我們可以對一個函數應用多個裝飾器,這時我們需要留心的是應用裝飾器的順序對結果會產生。影響比如:
def printdebug(func):def __decorator(): print('enter the login')func() print('exit the login')return __decorator def others(func): #define a other decoratordef __decorator():print '***other decorator***'func()return __decorator@others #apply two of decorator@printdebugdef login():print('in login:')@printdebug #switch decorator order@othersdef logout():print('in logout:')login()print('---------------------------') logout()
我們定義了另一個裝飾器others,然後我們對login函數和logout函數分別應用這兩個裝飾器。應用方式很簡單,在函數定義是直接用兩個@@就可以了。我們看一下上面代碼的輸出:
$ python deoc.py***other decorator***enter the loginin login:exit the login---------------------------enter the login***other decorator***in logout:exit the login
我們看到兩個裝飾器都已經成功應用上去了,不過輸出卻不相同。造成這個輸出不同的原因是我們應用裝飾器的順序不同。回頭看看我們login的定義,我們是先應用others,然後才是printdebug。而logout函數真好相反,發生了什嗎?如果你仔細看logout函數的輸出結果,可以看到裝飾器的遞迴。從輸出可以看出:logout函數先應用printdebug,列印出“enter the login”。printdebug的__decorator調用中間應用了others的__decorator,列印出“***other decorator***”。其實在邏輯上我們可以將logout函數應用裝飾器的過程這樣看(虛擬碼):
@printdebug #switch decorator order(@others(def logout():print('in logout:')))
我們解釋一下整個遞迴應用decorator的過程:
[printdebug decorated]logout() =>
printdebug.__decorator[call [others decorated]logout() ] =>
printdebug.__decorator.other.__decorator[call real logout]
7,靈活運用
什麼情況下裝飾器不適用?裝飾器不能對函數的一部分應用,只能作用於整個函數。
login函數是一個整體,當我們想對部分函數應用裝飾器時,裝飾器變的無從下手。比如我們想對下面這行語句應用裝飾器:
msg = "success" if user == "jatsz" else "fail"
怎麼辦?
一個變通的辦法是“提取函數”,我們將這行語句提取成函數,然後對提取出來的函數應用裝飾器:
def printdebug(func):def __decorator(user): print('enter the login')result = func(user) print('exit the login')return result return __decorator def login(user):print('in login:' + user)msg = validate(user) #exact to a methodreturn msg @printdebug #apply the decorator for exacted methoddef validate(user):msg = "success" if user == "jatsz" else "fail" return msgresult1 = login('jatsz');print result1
來個更加真實的應用,有時候validate是個耗時的過程。為了提高應用的效能,我們會將validate的結果cache一段時間(30 seconds),藉助decorator和上面的方法,我們可以這樣實現:
import timedictcache = {}def cache(func):def __decorator(user): now = time.time()if (user in dictcache):result,cache_time = dictcache[user]if (now - cache_time) > 30: #cache expiredresult = func(user)dictcache[user] = (result, now) #cache the result by userelse:print('cache hits')else:result = func(user)dictcache[user] = (result, now)return result return __decorator def login(user):print('in login:' + user)msg = validate(user) return msg @cache #apply the cache for this slow validationdef validate(user):time.sleep(5) #simulate 10 second blockmsg = "success" if user == "jatsz" else "fail" return msgresult1 = login('jatsz'); print result1 result2 = login('jatsz'); print result2 #this login will return immediately by hit the cacheresult3 = login('candy'); print result3
Reference:
http://stackoverflow.com/questions/739654/understanding-python-decorators --Understanding Python decorators
http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html --Python裝飾器學習(九步入門)
http://www.python.org/dev/peps/pep-0318/ --PEP 318 -- Decorators for Functions and Methods