一、什麼是裝飾器html
在介紹裝飾器以前,咱們先來思考一個問題:使用Python語言進行程序設計時,若是咱們想擴展一個函數的功能,通常會怎麼作呢?python
好比,有一個名爲print_info函數,當前該函數內只作一些簡單的打印操做,如今咱們想擴展這個函數功能,如在發生錯誤時,咱們將錯誤行號傳入到該函數打印出來。緩存
def print_info(): print("Hello World")
看到這個問題,咱們的第一反應確定是從新修改這個函數。在print_info函數增長一個參數,用於接收外部傳入的錯誤行號,在函數內部增長打印行號信息語句。閉包
def print_info(err_line): print("Hello World") print("The Error Occurred Line Number: %d" %(err_line))
彷佛,經過修改函數內容,也能實現咱們的需求。但有沒有這種可能呢,不直接進行修改print_info函數內容,經過其餘的方式增長print_info函數的額外功能。答案確定是有的,能實現這種功能的就是咱們要講的裝飾器。
app
裝飾器究竟是什麼呢?使用比較嚴謹的語言來描述:函數
裝飾器本質上是一個Python函數,它可讓其餘函數在不須要作任何代碼變更的前提下增長額外功能,裝飾器的返回值也是一個函數對象。它常常用於有切面需求的場景,好比:插入日誌、性能測試、事務處理、緩存、權限校驗等場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,
咱們就能夠抽離出大量與函數功能自己無關的雷同代碼並繼續重用。
歸納的講,裝飾器的做用就是爲已經存在的函數或對象添加額外的功能。性能
二、怎麼寫一個裝飾器測試
咱們先來看一段這樣的代碼spa
1 import sys
2
3 def log(fcn):
4 def wrapper(*argc, **kw):
5 #New Operations Are Added Here
6 if len(argc) != 1:
7 print('Illegal parameter')
8 return -1
9 print("The Error Occurred Line Number: %d" %(argc[0]))
10 return fcn()
11 return wrapper
12
13 def print_info():
14 print("Hello World")
15
16 print_info = log(print_info)
17
18 def main():
19 print_info(sys._getframe().f_lineno) # sys._getframe().f_lineno表明當前的行號
20
21 if __name__ == "__main__":
22 main()# 添加功能並保持原函數名不變
log函數是一個「閉包」(關於什麼是「閉包」可參考「淺析Python閉包」),該函數有一個參數fcn,返回值是內部實現的wrapper函數。設計
比較重要的一條語句print_info = log(print_info),這裏經過對print_info變量賦值改變它原來指向的類型,起到了對print_info函數賦予了新功能。執行這句語句以後print_info變量再也不表示前面定義的print_info函數,而是表示log函數內實現的wrapper函數,但原來定義的print_info函數仍然存在。
最終調用的print_info函數,其實調用的是log中的wrapper函數。因爲「閉包」特性,雖然wrapper函數已經離開了創造它的環境log函數,但它仍然可使用log函數中的自由變量。wrapper函數中,打印了傳入的錯誤行號,並調用了傳入的原定義的print_info函數,這樣便作到了對定義的print_info函數內容作修改,調用print_info增長了額外功能。
運行結果
The Error Occurred Line Number: 19 # 打印出新增的對應行號信息,在19行被調用,因此傳入行號是19
Hello World #原print_info函數執行內容
上面的log函數其實已是一個裝飾器了,它對原函數作了包裝並返回了另一個函數,額外添加了一些功能。由於這樣寫實在不太優雅,新版本Python中支持了@語法糖,咱們能夠把它使用起來。
下面代碼等同於上面的寫法。
import sys def log(fcn): def wrapper(*argc, **kw): #New Operations Are Added Here if len(argc) != 1: print('Illegal parameter') return -1 print("The Error Occurred Line Number: %d" %(argc[0])) return fcn() return wrapper @log def print_info(): print("Hello World") #print_info = log(print_info) def main(): print_info(sys._getframe().f_lineno) if __name__ == "__main__": main()
把@log放到print_info函數定義處,至關於執行了print_info = log(print_info)語句
三、完整裝飾器的寫法
前面寫的裝飾器都沒有問題,可是還差最後一步,雖然裝飾器裝飾過的函數看上去名字沒變,其實已經變了。
def log(fcn): def wrapper(*args, **kw): print(fcn.__name__) return fcn() return wrapper @log def print_info(): print('Hello World') def main(): print_info() print(print_info.__name__) if __name__ == "__main__": main()
運行結果
print_info
Hello World
wrapper
>>>
上面print_info函數經log裝飾器裝飾後,它的__name__屬性已經從原來的‘print_info’變成了‘wrapper’
由於返回的那個wrapper()函數名字就是'wrapper',因此,須要把原始函數的__name__等屬性複製到wrapper()函數中,不然,有些依賴函數簽名的代碼執行就會出錯。
不須要在內部添加wrapper.__name__=fcn.__name__這樣的代碼,Python內置的functools.wraps就是幹這個事的,因此,一個完整的decorator的寫法以下:
import functools def log(fcn): @functools.wraps(fcn) def wrapper(*args, **kw): print(fcn.__name__) return fcn() return wrapper
四、高階一點裝飾器
4.1 帶參數裝飾器
若是裝飾器函數自己要傳入參數,那麼裝飾器就會是這樣的
import sys def log(text): def wrapper(fcn) def inner_wrapper(*argc, **kw): #New Operations Are Added Here print(text) if len(argc) != 1: print('Illegal parameter') return -1 print("The Error Occurred Line Number: %d" %(argc[0])) return fcn() return inner_wrapper return wrapper @log('With Parameter Decorator') def print_info(): print("Hello World") #print_info = log(print_info) def main(): print_info(sys._getframe().f_lineno) if __name__ == "__main__": main()
對於帶參數裝飾器,能夠這麼理解,當帶參數的裝飾器被裝飾在某個函數上時,好比上述代碼@log('With Parameter Decorator'),
log('With Parameter Decorator')實際上是一個函數,會立刻被執行,它返回的結果任是一個裝飾器wrapper。
也就是下面代碼其實等價於print_info = wrapper(print_info)
@log('With Parameter Decorator') def print_info(): print("Hello World")
4.2 裝飾器裝飾類方法
前面咱們介紹的都是用裝飾器裝飾函數,裝飾器同時也能夠裝飾類方法,下面來看如何用裝飾器裝飾類的方法
咱們定義了名爲simple_test的類,這個類有一個data屬性和get_data方法。類裏的實現很是簡單,get_data方法只返回data屬性值,並不進行其餘操做。同時,咱們還需定義了一個裝飾器裝飾類的get_data方法,裝飾器中根據數據獲取源來編寫其中功能,好比咱們這裏選擇從串口終端獲取數據,直接修改裝飾器的內容讓其獲取終端輸入數據。
這樣在設計一個類時,作到了將類中穩定的部分和常常要修改的部分獨立開。類設計完成後,咱們不須要更改類的源碼,在使用時只須要根據需求修改類方法的裝飾器就能實現咱們的需求。
def serial_data(fcn): def wrapper(self, *args, **kw): str = input('Please enter an integer: ') try: tmp = int(str) except ValueError: print('Invalid Value') return None self.data = tmp return fcn(self) return wrapper class simple_test(object): def __init__(self, _val = 0): self.data = _val @serial_data def get_data(self): return self.data def main(): obj = simple_test() val = obj.get_data() if val is not None: print('get Data: %d' %(val)) if __name__ == "__main__": main()
運行結果:
Please enter an integer: 100
get Data: 100
>>>