【裝飾器有什么用】
顧名思義,就是在原有的業務函數原封不動的同時卻能夠及其方便地為其增加額外的功能,避免改動原有的業務代碼產生不必要的bug從而增加工作量。
就好比家里辛辛苦苦裝修完畢,逛街的時候又發現了一些小的裝飾品特別喜歡。於是買回家直接找個合適的地方擺放即可,而不用重新裝修房子大動干戈,也就是起到了“裝飾”的作用。
典型的使用場景有:排查問題時追加一些額外的調試日志;代碼執行效率低的時候追加計時功能定位代碼;需要校驗權限的場景;等等
【什么是裝飾器】
裝飾器的實質就是一個閉包函數,而閉包函數的特殊之處在於:
-
- 是個多層嵌套定義的函數
- 參數就是要被“裝飾”的函數的名稱
- 內層函數引用了外層函數定義的變量或者傳參
- 外層函數return返回了內層函數的引用(注意:正確形式是return functionName這種,不是return functionName())
【裝飾器的代碼形式】
在早些時候 (Python Version < 2.4,2004年以前),裝飾器的寫法類似下面形式:
def debug(func): def in_func(): print("starting") func() print("stopped") return in_func #@debug #等價於下面那種繁瑣的寫法:outputName=debug(outputName) def outputName(): print("\tHello,%s" % ("Elon Musk")) outputName=debug(outputName) #裝飾器原來的調用方式 if __name__ == "__main__": outputName()
在之后版本的Python中支持了@語法糖,上面的代碼等同於下面這種更加簡潔的寫法:
def debug(func): def in_func(): print("starting") func() print("stopped") return in_func @debug #等價於下面那種繁瑣的寫法:outputName=debug(outputName) def outputName(): print("\tHello,%s" % ("Elon Musk")) #outputName=debug(outputName) #裝飾器原來的調用方式 if __name__ == "__main__": outputName()
【裝飾可以傳參的函數】
上面示例被裝飾器裝飾的函數過於簡單,因為不需要傳參。
但是,實際工作中因為業務的復雜度往往需要參數傳遞。接下來,我將繼續優化示例代碼,給大家展示如何裝飾需要傳遞參數的函數:
def debug(func): def in_func(name): #內層函數直接給一個參數 print("starting") func(name) #在內層函數直接調用執行目標函數 print("stopped") return in_func @debug #等價於下面那種繁瑣的寫法:outputName=debug(outputName) def outputName(name): #函數的定義需要傳參 print("\tHello,%s" % (name)) #outputName=debug(outputName) #裝飾器原來的調用方式 if __name__ == "__main__": outputName("Elon Musk")
上面的例子中,裝飾器已經可以裝飾需要傳遞參數的函數了。
但是,實際工作中的業務代碼及其復雜,傳遞的參數數量很多且類型多種多樣,怎么辦呢?此時,我們需要使用到Python中的可變參數*arg、**kwarg:
def debug(func): def in_func(*arg,**kwarg): #內層函數直接萬能的可變參數 print("starting") func(*arg,**kwarg) #在內層函數直接調用萬能的可變參數 print("stopped") return in_func @debug #等價於下面那種繁瑣的寫法:outputName=debug(outputName) def outputName(name): #函數的定義需要傳參 print("\tHello,%s" % (name)) #outputName=debug(outputName) #裝飾器原來的調用方式 if __name__ == "__main__": outputName("Elon Musk")
以上代碼的執行結果如下:
【可以傳參的裝飾器】
至此,大家已經掌握了裝飾器的基本用法,恭喜恭喜!
不過,以上示例中的裝飾器比較簡單,因為裝飾器本身不能傳參,目的只是為了用最簡潔的代碼給大家展示清楚裝飾器的基本使用。
接下來,我將繼續優化示例代碼,給大家展示裝飾器如何傳遞參數:
import time def debug(n): #最外層函數用來接收裝飾器的傳參 def func1(func): #內部第一層函數才是真正的裝飾器 def func2(*args,**kwargs): #內層函數直接萬能的可變參數 for i in range(1,n+1,1): time.sleep(1) print("Time flies: %d" % (i)) print("starting") func(*args,**kwargs) #在內層函數直接調用萬能的可變參數 print("stopped") return func2 #裝飾器除了最內層的函數,其余外層的函數都需要return函數名 return func1 #裝飾器除了最內層的函數,其余外層的函數都需要return函數名 @debug(3) #裝飾器攜帶參數,讓sleep N秒后再執行目標函數 def outputName(name): print("\tHello,%s" % (name)) if __name__ == "__main__": outputName("Elon Musk")
運行結果如下:
可見,要想裝飾器本身能夠傳參,只需要在無參裝飾器之外再封裝一層即可。
為什么是這樣呢?其實,前面通過@debug(3)來裝飾outputName(name)相當於執行了語句outputName=debug(3)(outputName)。其原理本質可以分解為以下3個步驟:
-
- 先執行函數debug(3),此時返回閉包函數func1
-
接着執行函數func1(outputName),此時返回內層函數func2
-
再執行賦值語句outputName=func2(outputName)
現在,我們再來看看改寫之后的代碼及其運行結果: