想理解Python的decorator首先要知道在Python中函數也是一個對象,因此你能夠html
函數在Python中和變量的用法同樣也是一等公民,也就是高階函數(High Order Function)。全部的魔法都是由此而來。python
咱們想在函數login中輸出調試信息,咱們能夠這樣作app
def login(): print('in login') def printdebug(func): print('enter the login') func() print('exit the login') printdebug(login)
這個方法討厭的是每次調用login是,都經過printdebug來調用,但畢竟這是可行的。函數
既然函數能夠做爲返回值,能夠賦值給變量,咱們可讓代碼優美一點。性能
def login(): print('in login') def printdebug(func): def __decorator(): print('enter the login') func() print('exit the login') return __decorator #function as return value debug_login = printdebug(login) #function assign to variable debug_login() #execute the returned function
這樣咱們每次只要調用debug_login就能夠了,這個名字更符合直覺。咱們將原先的兩個函數printdebug和login綁定到一塊兒,成爲debug_login。這種耦合叫內聚:-)。學習
printdebug和login是經過debug_login = printdebug(login)這一句來結合的,這一句彷佛也是多餘的,能不能在定義login是加個標註,從而將printdebug和login結合起來?ui
上面的代碼從語句組織的角度來說很難再優美了,Python的解決方案是提供一個語法糖(Syntax Sugar),用一個@符號來結合它們。this
def printdebug(func): def __decorator(): print('enter the login') func() print('exit the login') return __decorator @printdebug #combine the printdebug and login def login(): print('in login') login() #make the calling point more intuitive
能夠看出decorator就是一個:使用函數做參數而且返回函數的函數。經過改進咱們能夠獲得:spa
在Python解釋器發現login調用時,他會將login轉換爲printdebug(login)()。也就是說真正執行的是__decorator這個函數。debug
1,login函數帶參數
login函數可能有參數,好比login的時候傳人user的信息。也就是說,咱們要這樣調用login:
login(user)
Python會將login的參數直接傳給__decorator這個函數。咱們能夠直接在__decorator中使用user變量:
def printdebug(func): def __decorator(user): #add parameter receive the user information print('enter the login') func(user) #pass user to login print('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 parameter def printdebug(func): def __decorator(user): print('enter the login, and debug level is: ' + str(level)) #print debug level func(user) print('exit the login') return __decorator return printdebug #return original decorator @printdebug_level(level=5) #decorator's parameter, debug level set to 5 def 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’)
有時候login會有返回值,好比返回message來代表login是否成功。
1
|
login_result
=
login(‘jatsz’)
|
咱們須要將返回值在decorator和調用函數間傳遞:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
def
printdebug(func):
def
__decorator(user):
print
(
'enter the login'
)
result
=
func(user)
#recevie the native function call result
print
(
'exit the login'
)
return
result
#return to caller
return
__decorator
@printdebug
def
login(user):
print
(
'in login:'
+
user)
msg
=
"success"
if
user
=
=
"jatsz"
else
"fail"
return
msg
#login with a return value
result1
=
login(
'jatsz'
);
print
result1
#print login result
result2
=
login(
'candy'
);
print
result2
|
咱們解釋一下返回值的傳遞過程:
...omit for brief…[real][msg from login(‘jatsz’) => [result from]__decorator => [assign to] result1
咱們能夠對一個函數應用多個裝飾器,這時咱們須要留心的是應用裝飾器的順序對結果會產生。影響好比:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
def
printdebug(func):
def
__decorator():
print
(
'enter the login'
)
func()
print
(
'exit the login'
)
return
__decorator
def
others(func):
#define a other decorator
def
__decorator():
print
'***other decorator***'
func()
return
__decorator
@others
#apply two of decorator
@printdebug
def
login():
print
(
'in login:'
)
@printdebug
#switch decorator order
@others
def
logout():
print
(
'in logout:'
)
login()
print
(
'---------------------------'
)
logout()
|
咱們定義了另外一個裝飾器others,而後咱們對login函數和logout函數分別應用這兩個裝飾器。應用方式很簡單,在函數定義是直接用兩個@@就能夠了。咱們看一下上面代碼的輸出:
1
2
3
4
5
6
7
8
9
10
|
$ python deoc.py
***other decorator***
enter the login
in 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函數應用裝飾器的過程這樣看(僞代碼):
1
2
3
4
5
6
7
8
|
@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]
什麼狀況下裝飾器不適用?裝飾器不能對函數的一部分應用,只能做用於整個函數。
login函數是一個總體,當咱們想對部分函數應用裝飾器時,裝飾器變的無從下手。好比咱們想對下面這行語句應用裝飾器:
1
|
msg
=
"success"
if
user
=
=
"jatsz"
else
"fail"
|
怎麼辦?
一個變通的辦法是「提取函數」,咱們將這行語句提取成函數,而後對提取出來的函數應用裝飾器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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 method
return
msg
@printdebug
#apply the decorator for exacted method
def
validate(user):
msg
=
"success"
if
user
=
=
"jatsz"
else
"fail"
return
msg
result1
=
login(
'jatsz'
);
print
result1
|
來個更加真實的應用,有時候validate是個耗時的過程。爲了提升應用的性能,咱們會將validate的結果cache一段時間(30 seconds),藉助decorator和上面的方法,咱們能夠這樣實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import
time
dictcache
=
{}
def
cache(func):
def
__decorator(user):
now
=
time.time()
if
(user
in
dictcache):
result,cache_time
=
dictcache[user]
if
(now
-
cache_time) >
30
:
#cache expired
result
=
func(user)
dictcache[user]
=
(result, now)
#cache the result by user
else
:
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 validation
def
validate(user):
time.sleep(
5
)
#simulate 10 second block
msg
=
"success"
if
user
=
=
"jatsz"
else
"fail"
return
msg
result1
=
login(
'jatsz'
);
print
result1
result2
=
login(
'jatsz'
);
print
result2
#this login will return immediately by hit the cache
result3
=
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