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裝飾器的一些使用,裝飾器的代碼還是比較容易理解的。只要通過一些例子進行實際操作一下,就很容易理解了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章