Python中的裝飾器(decorator)


想理解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 value

debug_login = printdebug(login)  #function assign to variable

debug_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 login
def 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 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’)

 

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 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

6,應用多個裝飾器

我們可以對一個函數應用多個裝飾器,這時我們需要留心的是應用裝飾器的順序對結果會產生。影響比如:

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函數分別應用這兩個裝飾器。應用方式很簡單,在函數定義是直接用兩個@@就可以了。我們看一下上面代碼的輸出:

$ 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函數應用裝飾器的過程這樣看(偽代碼):

@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 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和上面的方法,我們可以這樣實現:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM