您好,登錄后才能下訂單哦!
本文實例講述了Python函數裝飾器原理與用法。分享給大家供大家參考,具體如下:
裝飾器本質上是一個函數,該函數用來處理其他函數,它可以讓其他函數在不需要修改代碼的前提下增加額外的功能,裝飾器的返回值也是一個函數對象。它經常用于有切面需求的場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗等應用場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼并繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。
嚴格來說,裝飾器只是語法糖,裝飾器是可調用的對象,可以像常規的可調用對象那樣調用,特殊的地方是裝飾器的參數是一個函數
現在有一個新的需求,希望可以記錄下函數的執行時間,于是在代碼中添加日志代碼:
import time #遵守開放封閉原則 def foo(): start = time.time() # print(start) # 1504698634.0291758從1970年1月1號到現在的秒數,那年Unix誕生 time.sleep(3) end = time.time() print('spend %s'%(end - start)) foo()
bar()、bar2()也有類似的需求,怎么做?再在bar函數里調用時間函數?這樣就造成大量雷同的代碼,為了減少重復寫代碼,我們可以這樣做,重新定義一個函數:專門設定時間:
import time def show_time(func): start_time=time.time() func() end_time=time.time() print('spend %s'%(end_time-start_time)) def foo(): print('hello foo') time.sleep(3) show_time(foo)
但是這樣的話,你基礎平臺的函數修改了名字,容易被業務線的人投訴的,因為我們每次都要將一個函數作為參數傳遞給show_time函數。而且這種方式已經破壞了原有的代碼邏輯結構,之前執行業務邏輯時,執行運行foo(),但是現在不得不改成show_time(foo)。那么有沒有更好的方式的呢?當然有,答案就是裝飾器。
def show_time(f): def inner(): start = time.time() f() end = time.time() print('spend %s'%(end - start)) return inner @show_time #foo=show_time(f) def foo(): print('foo...') time.sleep(1) foo() def bar(): print('bar...') time.sleep(2) bar()
輸出結果:
foo...
spend 1.0005607604980469
bar...
函數show_time就是裝飾器,它把真正的業務方法f包裹在函數里面,看起來像foo被上下時間函數裝飾了。在這個例子中,函數進入和退出時 ,被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。
@符號是裝飾器的語法糖,在定義函數的時候使用,避免再一次賦值操作
裝飾器在Python使用如此方便都要歸因于Python的函數能像普通的對象一樣能作為參數傳遞給其他函數,可以被賦值給其他變量,可以作為返回值,可以被定義在另外一個函數內。
裝飾器有2個特性,一是可以把被裝飾的函數替換成其他函數, 二是可以在加載模塊時候立即執行
def decorate(func): print('running decorate', func) def decorate_inner(): print('running decorate_inner function') return func() return decorate_inner @decorate def func_1(): print('running func_1') if __name__ == '__main__': print(func_1) #running decorate <function func_1 at 0x000001904743DEA0> # <function decorate.<locals>.decorate_inner at 0x000001904743DF28> func_1() #running decorate_inner function # running func_1
通過args 和 *kwargs 傳遞被修飾函數中的參數
def decorate(func): def decorate_inner(*args, **kwargs): print(type(args), type(kwargs)) print('args', args, 'kwargs', kwargs) return func(*args, **kwargs) return decorate_inner @decorate def func_1(*args, **kwargs): print(args, kwargs) if __name__ == '__main__': func_1('1', '2', '3', para_1='1', para_2='2', para_3='3') #返回結果 #<class 'tuple'> <class 'dict'> # args ('1', '2', '3') kwargs {'para_1': '1', 'para_2': '2', 'para_3': '3'} # ('1', '2', '3') {'para_1': '1', 'para_2': '2', 'para_3': '3'}
帶參數的被裝飾函數
import time # 定長 def show_time(f): def inner(x,y): start = time.time() f(x,y) end = time.time() print('spend %s'%(end - start)) return inner @show_time def add(a,b): print(a+b) time.sleep(1) add(1,2)
不定長
import time #不定長 def show_time(f): def inner(*x,**y): start = time.time() f(*x,**y) end = time.time() print('spend %s'%(end - start)) return inner @show_time def add(*a,**b): sum=0 for i in a: sum+=i print(sum) time.sleep(1) add(1,2,3,4)
帶參數的裝飾器
在上面的裝飾器調用中,比如@show_time,該裝飾器唯一的參數就是執行業務的函數。裝飾器的語法允許我們在調用時,提供其它參數,比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。
import time def time_logger(flag=0): def show_time(func): def wrapper(*args, **kwargs): start_time = time.time() func(*args, **kwargs) end_time = time.time() print('spend %s' % (end_time - start_time)) if flag: print('將這個操作的時間記錄到日志中') return wrapper return show_time @time_logger(flag=1) def add(*args, **kwargs): time.sleep(1) sum = 0 for i in args: sum += i print(sum) add(1, 2, 5)
@time_logger(flag=1) 做了兩件事:
(1)time_logger(1):得到閉包函數show_time,里面保存環境變量flag
(2)@show_time :add=show_time(add)
上面的time_logger是允許帶參數的裝飾器。它實際上是對原有裝飾器的一個函數封裝,并返回一個裝飾器(一個含有參數的閉包函數)。當我 們使用@time_logger(1)調用的時候,Python能夠發現這一層的封裝,并把參數傳遞到裝飾器的環境中。
疊放裝飾器
執行順序是什么
如果一個函數被多個裝飾器修飾,其實應該是該函數先被最里面的裝飾器修飾后(下面例子中函數main()先被inner裝飾,變成新的函數),變成另一個函數后,再次被裝飾器修飾
def outer(func): print('enter outer', func) def wrapper(): print('running outer') func() return wrapper def inner(func): print('enter inner', func) def wrapper(): print('running inner') func() return wrapper @outer @inner def main(): print('running main') if __name__ == '__main__': main() #返回結果 # enter inner <function main at 0x000001A9F2BCDF28> # enter outer <function inner.<locals>.wrapper at 0x000001A9F2BD5048> # running outer # running inner # running main
類裝飾器
相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器還可以依靠類內部的__call__方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。
import time class Foo(object): def __init__(self, func): self._func = func def __call__(self): start_time=time.time() self._func() end_time=time.time() print('spend %s'%(end_time-start_time)) @Foo #bar=Foo(bar) def bar(): print ('bar') time.sleep(2) bar() #bar=Foo(bar)()>>>>>>>沒有嵌套關系了,直接active Foo的 __call__方法
標準庫中有多種裝飾器
例如:裝飾方法的函數有property, classmethod, staticmethod; functools模塊中的lru_cache, singledispatch, wraps 等等
from functools import lru_cache
from functools import singledispatch
from functools import wraps
functools.wraps使用裝飾器極大地復用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表,先看例子:
def foo(): print("hello foo") print(foo.__name__)# foo def logged(func): def wrapper(*args, **kwargs): print (func.__name__ + " was called") return func(*args, **kwargs) return wrapper @logged def cal(x): resul=x + x * x print(resul) cal(2) #6 #cal was called print(cal.__name__)# wrapper print(cal.__doc__)#None #函數f被wrapper取代了,當然它的docstring,__name__就是變成了wrapper函數的信息了。
好在我們有functools.wraps,wraps本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數一樣的元信息了。
from functools import wraps def logged(func): @wraps(func) def wrapper(*args, **kwargs): print(func.__name__ + " was called") return func(*args, **kwargs) return wrapper @logged def cal(x): return x + x * x print(cal.__name__) # cal
使用裝飾器會產生我們可能不希望出現的副作用, 例如:改變被修飾函數名稱,對于調試器或者對象序列化器等需要使用內省機制的那些工具,可能會無法正常運行;
其實調用裝飾器后,會將同一個作用域中原來函數同名的那個變量(例如下面的func_1),重新賦值為裝飾器返回的對象;使用@wraps后,會把與內部函數(被修飾函數,例如下面的func_1)相關的重要元數據全部復制到外圍函數(例如下面的decorate_inner)
from functools import wraps def decorate(func): print('running decorate', func) @wraps(func) def decorate_inner(): print('running decorate_inner function', decorate_inner) return func() return decorate_inner @decorate def func_1(): print('running func_1', func_1) if __name__ == '__main__': func_1() #輸出結果 #running decorate <function func_1 at 0x0000023E8DBD78C8> # running decorate_inner function <function func_1 at 0x0000023E8DBD7950> # running func_1 <function func_1 at 0x0000023E8DBD7950>
關于Python相關內容感興趣的讀者可查看本站專題:《Python函數使用技巧總結》、《Python面向對象程序設計入門與進階教程》、《Python數據結構與算法教程》、《Python字符串操作技巧匯總》、《Python編碼操作技巧總結》及《Python入門與進階經典教程》
希望本文所述對大家Python程序設計有所幫助。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。