裝飾器本質上是一個Python函數,它可以讓其他函數在不需要做任何程式碼變動的前提下增加額外功能,裝飾器的回傳值也是一個函數物件.
常用於有切面需求的場景,例如:插入日誌、效能測試、交易處理、快取、權限校驗等場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同程式碼並繼續重複使用。
先來看一個簡單範例:
def now(): print('2017_7_29')
現在有一個新的需求,希望可以記錄下函數的執行日誌,於是在程式碼中加入日誌程式碼:
def now(): print('2017_7_29') logging.warn("running")
假設有類似的多個需求,怎麼做?再寫一個logging在now函數裡?這樣就造成大量雷同的程式碼,為了減少重複寫程式碼,我們可以這樣做,重新定義一個函數:專門處理日誌,日誌處理完之後再執行真正的業務程式碼.
def use_logging(func): logging.warn("%s is running" % func.__name__) func() def now(): print('2017_7_29') use_logging(now)
在實作,邏輯上不難, 但是這樣的話,我們每次都要將一個函數當作參數傳遞給日誌函數。而且這種方式已經破壞了原有的程式碼邏輯結構,之前執行業務邏輯時,執行運行now(),但是現在不得不改成use_logging(now)。
那麼有沒有更好的方式的呢?當然有,答案就是裝飾器。
首先要明白函數也是一個對象,而且函數物件可以被賦值給變數,所以,透過變數也能呼叫該函數。例如:
(=
簡單裝飾器
#本質上,decorator就是回傳函數的高階函數。所以,我們要定義一個能列印日誌的decorator,可以定義如下:
def log(func): def wrapper(*args,**kw): print('call %s():'%func.__name__) return func(*args,**kw) return wrapper # 由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在, # 只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。 # wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。 # 在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
上面的log
,因為它是一個decorator,所以接受一個函數作為參數,並傳回一個函數.現在執行:
now = log(now) now()
输出结果: call now(): 2017_7_28
函數log就是裝飾器,它把執行真正業務方法的func包裹在函數裡面,看起來像now被log裝飾了。在這個例子中,函數進入時 ,被稱為一個橫切面(Aspect),這種程式設計方式被稱為面向切面的程式設計(Aspect-Oriented Programming)。
使用語法糖:
@logdef now(): print('2017_7_28')
@符號是裝飾器的語法糖,在定義函數的時候使用,避免再一次賦值運算
這樣我們就可以省去now = log(now)這句話了,直接呼叫now()即可得到想要的結果。如果我們有其他的類似函數,我們可以繼續呼叫裝飾器來修飾函數,而不用重複修改函數或增加新的封裝。這樣,我們就提高了程式的可重複利用性,並增加了程式的可讀性。
裝飾器在Python使用如此方便都要歸因於Python的函數能像普通的物件一樣能作為參數傳遞給其他函數,可以被賦值給其他變量,可以作為返回值,可以被定義在另外一個函數內。
有參數的裝飾器:
#如果decorator本身需要傳入參數,那就需要寫一個傳回decorator的高階函數,寫出來會複雜一點。例如,要自訂log的文字:
def log(text): def decorator(func): def wrapper(*args,**kw): print('%s %s()'%(text,func.__name__)) return func(*args,**kw) return wrapper return decorator
這個3層嵌套的decorator用法如下:
@log(() now()
等價於
<span style="color: #000000;">now = log('goal')(now)<br># 首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数<br>now()</span>
因為我們講了函數也是對象,它有__name__
等屬性,但你去看經過decorator裝飾之後的函數,它們的__name__
已經從原來的'now'
變成了'wrapper'
:
print(now.__name__)# wrapper
因為傳回的那個wrapper()
函數名稱就是'wrapper'
,所以,需要把原始函數的__name__
等屬性複製到wrapper()
函數中,否則,有些依賴函數簽名的程式碼執行就會出錯。
不需要寫wrapper.__name__ = func.__name__
這樣的程式碼,Python內建的functools.wraps
就是做這個事的,所以,一個完整的decorator的寫法如下:
import functools def log(func): @functools.wraps(func) def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper
import functools def log(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator
類別裝飾器:
再來看看類別裝飾器,相較於函數裝飾器,類別裝飾器具有靈活度大、高內聚、封裝性等優點。使用類別裝飾器也可以依賴類別內部的__call__方法,當使用@ 形式將裝飾器附加到函數上時,就會呼叫此方法
import time class Foo(object): def __init__(self, func): self._func = func def __call__(self): print ('class decorator runing') self._func() print ('class decorator ending') @Foo def now(): print (time.strftime('%Y-%m-%d',time.localtime(time.time()))) now()
總結:
概括的講,裝飾器的作用就是為已經存在的物件添加額外的功能。
同時在物件導向(OOP)的設計模式中,decorator被稱為裝飾模式。 OOP的裝飾模式需要透過繼承和組合來實現,而Python除了能支援OOP的decorator外,直接從語法層次支援decorator。 Python的decorator可以用函數實現,也可以用類別實作。
更多相關知識請關注python影片教學欄位
以上是Python裝飾器詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!