一份來自 StackOverflow 的最佳 Python 裝飾器教程

可樂發表於2019-04-21

注意: 這是一篇 StackOverflow 上的問題回答,因為這個回答很棒,所以我把它存檔了


問: 怎樣在 Python 中連續使用多個函式裝飾器?


如果你不想看詳細的解釋,你可以看 Paolo Bergantino 的回答

裝飾器基礎

Python 的裝飾器都是物件

為了理解裝飾器,你首先必須知道 Python 中的函式都是 object 物件。 這非常重要。讓我們通過一個例子來看看原因。

記住上面的內容,一會我們還會用得到。

Python 函式另一個有趣的性質在於它們可以。。。在另一個函式內部定義!

函式引用

現在是比較有趣的部分。。。

你已經知道了函式是 object 物件。此外,函式還:

  • 可以像變數一樣賦值
  • 可以在另一個函式內部定義

這表示 函式可以 return 另一個函式。看下面吧!☺

但等等…還有一些內容!

如果你可以 return 一個函式,那麼你也可以把函式當作引數傳遞:

好,你已經掌握了裝飾器所需的全部知識。正如你所見,裝飾器是“包裝器”,也就是說 它們允許你在它們裝飾的函式的前面和後面執行其他程式碼 ,而不必修改函式本身。

動手製作裝飾器

你應該怎樣動手製作:

現在,你希望每次你呼叫 a_stand_alone_function 的時候,實際上 a_stand_alone_function_decorated 會被呼叫。也就是說,這只是用 my_shiny_new_decorator 返回的函式重寫了 a_stand_alone_function 函式:

裝飾器解密

和前面相同的例子,但是使用了裝飾器語法:

就是這樣,裝飾器就是這麼簡單。 @decorator 只是下面形式的簡寫:

裝飾器只是一個 pythonic 的裝飾器設計模式的變種。Python 中內建了許多種傳統的設計模式來簡化開發過程(例如迭代器)。

當然,你可以疊加多個裝飾器:

使用 Python 的裝飾器語法:

你設定裝飾器的順序很重要:


現在:是時候回答問題了。。。

現在你很容易就知道怎樣回答這個問題了:

現在你該放下輕鬆的心態,好好看看裝飾器的高階使用方法了。


把裝飾器傳到下一層去

把引數傳遞給被裝飾的函式

裝飾器方法

關於 Python 的一個優點就是方法和函式本質本質上是一樣的。二者唯一的區別就是方法的第一個引數是對當前物件的引用 (self)。

這意味著你可以按照同樣的方式為方法建立裝飾器!只要記得考慮 self 就可以了:

如果你在建立通用的裝飾器 — 一個適用於任何函式或者方法的裝飾器,無論引數是什麼 — 那麼只要使用 *args, **kwargs就可以了:

把引數傳遞給裝飾器

太棒了,現在你對於把引數傳遞給裝飾器本身有什麼看法呢?

這可能有點奇怪,因為裝飾器必須接收一個函式作為引數。因此,你可能無法直接把裝飾器函式作為引數傳遞給另一個裝飾器。

在得到答案之前,讓我們寫一個小的例子:

結果是一模一樣的:my_decorator 被呼叫了。因此當你使用 @my_decorator 時,Python 會呼叫 my_decorator” 變數所代表的函式

這很重要!你提供的這個變數可以指向裝飾器,也可以不指向

讓我們增加點難度。 ☺

沒什麼意料之外的事情發生。

我們再做一次上面的事情,只不過這一次取消掉所有的中間變數:

讓它更短一下

你注意到了嗎?我們呼叫了一個 @ 語法的函式! :-)

所以,回到裝飾器的引數上面來。如果我們可以使用函式生成一個臨時的裝飾器,我們也可以把引數傳遞給那個函式,對嗎?

最後得到的就是:帶引數的裝飾器。引數可以設定為變數:

如你所見,你可以使用這個技巧向裝飾器傳遞引數,就像是向普通函式傳遞一樣。如果你願意的話,你甚至可以使用 *args, **kwargs。但記住,裝飾器只會被呼叫一次。只在 Python 匯入指令碼的時候執行。在這之後你就無法動態設定引數了。當你執行 import x 之後,函式已經被裝飾了,因此之後你無法改變任何東西。


練習: 裝飾一個裝飾器

好的,作為獎勵,我會提供你一段程式碼允許裝飾器接收任何引數。畢竟,為了接收引數,我們會用另一個函式建立裝飾器。

我們包裝一下裝飾器。

我們最近看到的有包裝函式的還有什麼呢?

對了,就是裝飾器!

讓我們做點有趣的事,寫一個裝飾器的裝飾器:

可以像下面這樣使用:

我知道,上次你有這種感覺,是在聽一個人說:“在理解遞迴之前,你必須首先理解遞迴” 時。但現在,掌握了這個之後你不覺得很棒嗎?


最佳實踐: 裝飾器

  • 裝飾器在 Python 2.4 引進,因此確保你的程式碼執行的 Python 版本 >=2.4
  • 裝飾器會拖慢函式呼叫速度。請牢記
  • 你無法解除裝飾一個函式。 (確實 一些技巧可以建立允許解除裝飾的裝飾器,但是沒人會使用它們。)因此一旦函式被裝飾了,所有這個函式的程式碼就都裝飾了。
  • 裝飾器包裝函式,會使得函式更難除錯。 (從 Python >=2.5 有所好轉;看下文。)

functools 模組在 Python 2.5 引進。模組中包含了函式 functools.wraps() ,這個函式會把被裝飾函式的名字,模組名,docstring 都複製到它的包裝器中。

(有趣的事情是: functools.wraps() 是個裝飾器!☺)


怎樣使裝飾器變得有用?

現在最大的問題是: 我可以用裝飾器來幹嘛?

裝飾器看起來很酷,很強大,但有一個實用的例子就更好了。大概有 1000 種可能的例子。常見的使用方法是擴充套件一個外部庫函式(你無法修改)的行為,或者用來除錯外部庫函式(你不想修改它,因為它是臨時函式)。

你可以使用裝飾器以 DRY(Don’t Repeat Yourself,不重複自己) 的方式擴充套件函式,就像這樣:

當然,裝飾器的優點就在於你可以在不重寫函式的前提下,使用在幾乎任何函式上。DRY(Don’t Repeat Yourself,不要重複你自己),正如我說的:

Python 本身提供了幾種裝飾器: propertystaticmethod,等

  • Django 使用裝飾器來管理快取,檢視許可權。
  • Twisted 用它來偽造內聯非同步函式呼叫。

裝飾器的用途確實很廣。

相關文章