Python裝飾器 Python裝飾器
Python裝飾器
裝飾模式有很多經典的使用場景,例如插入日誌、效能測試、事務處理等等,有了裝飾器,就可以提取大量函式中與本身功能無關的類似程式碼,從而達到程式碼重用的目的。下面就一步步看看Python中的裝飾器。
一個簡單的需求
現在有一個簡單的函式"myfunc",想通過程式碼得到這個函式的大概執行時間。
我們可以直接把計時邏輯方法"myfunc"內部,但是這樣的話,如果要給另一個函式計時,就需要重複計時的邏輯。所以比較好的做法是把計時邏輯放到另一個函式中("deco"),如下:
但是,上面的做法也有一個問題,就是所有的"myfunc"呼叫處都要改為"deco(myfunc)"。
下面,做一些改動,來避免計時功能對"myfunc"函式呼叫程式碼的影響:
經過了上面的改動後,一個比較完整的裝飾器(deco)就實現了,裝飾器沒有影響原來的函式,以及函式呼叫的程式碼。例子中值得注意的地方是,Python中一切都是物件,函式也是,所以程式碼中改變了"myfunc"對應的函式物件。
裝飾器語法糖
在Python中,可以使用"@"語法糖來精簡裝飾器的程式碼:
使用了"@"語法糖後,我們就不需要額外程式碼來給"myfunc"重新賦值了,其實"@deco"的本質就是"myfunc = deco(myfunc)",當認清了這一點後,後面看帶引數的裝飾器就簡單了。
被裝飾的函式帶引數
前面的例子中,被裝飾函式的本身是沒有引數的,下面看一個被裝飾函式有引數的例子:
從例子中可以看到,對於被裝飾函式需要支援引數的情況,我們只要使裝飾器的內嵌函式支援同樣的簽名即可。
也就是說這時,"addFunc(3, 8) = deco(addFunc(3, 8))"。
這裡還有一個問題,如果多個函式擁有不同的引數形式,怎麼共用同樣的裝飾器?在Python中,函式可以支援(*args, **kwargs)可變引數,所以裝飾器可以通過可變引數形式來實現內嵌函式的簽名。
帶引數的裝飾器
裝飾器本身也可以支援引數,例如說可以通過裝飾器的引數來禁止計時功能:
通過例子可以看到,如果裝飾器本身需要支援引數,那麼裝飾器就需要多一層的內嵌函式。
這時候,"addFunc(3, 8) = deco(True)( addFunc(3, 8))","myFunc() = deco(False)( myFunc ())"。
裝飾器呼叫順序
裝飾器是可以疊加使用的,那麼這是就涉及到裝飾器呼叫順序了。對於Python中的"@"語法糖,裝飾器的呼叫順序與使用 @ 語法糖宣告的順序相反。
在這個例子中,"addFunc(3, 8) = deco_1(deco_2(addFunc(3, 8)))"。
Python內建裝飾器
在Python中有三個內建的裝飾器,都是跟class相關的:staticmethod、classmethod 和property。
- staticmethod 是類靜態方法,其跟成員方法的區別是沒有 self 引數,並且可以在類不進行例項化的情況下呼叫
- classmethod 與成員方法的區別在於所接收的第一個引數不是 self (類例項的指標),而是cls(當前類的具體型別)
- property 是屬性的意思,表示可以通過通過類例項直接訪問的資訊
對於staticmethod和classmethod這裡就不介紹了,通過一個例子看看property。
注意,對於Python新式類(new-style class),如果將上面的 "@var.setter" 裝飾器所裝飾的成員函式去掉,則Foo.var 屬性為只讀屬性,使用 "foo.var = 'var 2'" 進行賦值時會丟擲異常。但是,對於Python classic class,所宣告的屬性不是 read-only的,所以即使去掉"@var.setter"裝飾器也不會報錯。
總結
本文介紹了Python裝飾器的一些使用,裝飾器的程式碼還是比較容易理解的。只要通過一些例子進行實際操作一下,就很容易理解了。