理解python中的裝飾器
正如其名,裝飾器的作用是為已經存在的對象增加額外功能(裝飾),由此可使已有函數在無需代碼改動的情況下增加額外功能;裝飾器的本質是嵌套的函數且返回函數對象,即閉包。有關閉包的概念,可參考《理解Python中的閉包》一文。
二 裝飾器應用場景舉例
在介紹裝飾器之前,我們思考下遇到如下場景時的解決思路,然後在此基礎上,描述裝飾器的意義和旨在解決哪些問題。
假如我們的func函數已經在使用,而且工作的挺好,func函數如下:
def func(): print("執行func代碼塊")
然而,某天針對該函數有新需求提出,暫時命名為需求
針對需求1,一種可以簡單理解的實現形式如下:
#實現1 增加執行耗時統計 def func(): start_time = time.time() print("執行func代碼塊") end_time = time.time() print("執行耗時:%s"%(end_time-start_time))
從實現來看,滿足了需求1,但如果其余的函數也提出了類似的需求,如func1、func2都需要增加耗時測量,按上面的實現方式,func1、func2也需要在原功能前後增加
針對代碼重復的問題,另一種實現方式是,定義一個專用的耗時測量函數,當需要測量某個函數時,直接將被測函數作為該測量函數的入參,實現形式如下:
# 實現2將公共代碼抽離,定義專用函數 def count_time(func_name): start_time = time.time() func_name() end_time = time.time() print("執行耗時:%s"%(end_time-start_time))
從上面實現來看,功能耗時統計的代碼被抽離並定義在共用函數
如:count_time(func) #對func函數進行耗時統計 如:count_time(func1) #對func1函數進行耗時統計
但這種實現也是有問題的,需求是希望直接調用func()函數即可完成對功能耗時的測量,但目前的實現方式,需要使用另外一個函數count_time,顯然改變了函數的調用方式。
三 裝飾器為原有函數增加額外功能
針對上面場景描述及問題分析,我們使用裝飾器解決該問題,首先定義裝飾器函數如下:
#定義裝飾器函數 def timer(func_name): def wrapper(*args,**kwargs): start_time = time.time() func_name() end_time = time.time() print("執行耗時:%s" % (end_time - start_time)) return wrapper
從裝飾器的定義來看,其實就是一個閉包實現,滿足了在上篇對閉包三個條件的定義:
1) timer函數內部定義了wrapper函數,滿足函數嵌套;
2) 內部函數wrapper使用了外部函數 timer的變量func_name;
3) 返回是一個內部函數的引用;
因此,裝飾器本質是函數,是閉包的應用。
現在,我們用裝飾器timer實現對func函數所提出的需求,即增加函數耗時測量功能。
使用裝飾器的方法如下:
func = timer(func)#1 func() #2
使用上面2步,調用func()時,雖然函數名相同,原func()函數也未做改動,但輸出結果已經增加了耗時統計功能。如果不理解,請繼續看上篇《理解Python中的閉包》中關於閉包的講解J。
另外,python的裝飾器有個更簡潔的表示方式,即使用“語法糖”@,如下:
@timer #@語法糖 相當於 test1 = timer(test1) 只能放在定義函數的上面 def func(): print("執行func代碼塊")
#使用裝飾器語法糖@的調用方式
func()#直接調用即可
四 裝飾器的作用
從上文分析,可以了解到,裝飾器主要用於為已存在的函數對象附件額外的功能,而原先的函數的內部實現可以不做改動,調用方式也保持不變,而這些附加的功能是可以抽離出來作為共用的,避免了相似場景下代碼的冗余。
其他資源分享:
關於裝飾器:請參看http://i.youku.com/weiworld521 第21節;
理解python中的裝飾器