Python @函數(shù)裝飾器及用法(超級詳細(xì))

前面章節(jié)中,我們已經(jīng)講解了 Python 內(nèi)置的 3 種函數(shù)裝飾器,分別是 @staticmethod、@classmethod 和 @property,其中 staticmethod()、classmethod() 和 property() 都是 Python 的內(nèi)置函數(shù)。

那么,函數(shù)裝飾器的工作原理是怎樣的呢?假設(shè)用 funA() 函數(shù)裝飾器去裝飾 funB() 函數(shù),如下所示:

<pre class="python sh_python snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

1.  #funA 作為裝飾器函數(shù)
2.  def funA(fn):
3.  #...
4.  fn() # 執(zhí)行傳入的fn參數(shù)
5.  #...
6.  return '...'

8.  @funA
9.  def funB():
10.  #...

</pre>

實際上,上面程序完全等價于下面的程序:

<pre class="python sh_python snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

1.  def funA(fn):
2.  #...
3.  fn() # 執(zhí)行傳入的fn參數(shù)
4.  #...
5.  return '...'

7.  def funB():
8.  #...

10.  funB = funA(funB)

</pre>

通過比對以上 2 段程序不難發(fā)現(xiàn),使用函數(shù)裝飾器 A() 去裝飾另一個函數(shù) B(),其底層執(zhí)行了如下 2 步操作:

  1. 將 B 作為參數(shù)傳給 A() 函數(shù);
  2. 將 A() 函數(shù)執(zhí)行完成的返回值反饋回 B。

舉個實例:

<pre class="python sh_python snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

1.  #funA 作為裝飾器函數(shù)
2.  def funA(fn):
3.  print("C語言中文網(wǎng)")
4.  fn() # 執(zhí)行傳入的fn參數(shù)
5.  print("http://c.biancheng.net")
6.  return "裝飾器函數(shù)的返回值"

8.  @funA
9.  def funB():
10.  print("學(xué)習(xí) Python")

</pre>

程序執(zhí)行流程為:

C語言中文網(wǎng)
學(xué)習(xí) Python
http://c.biancheng.net

在此基礎(chǔ)上,如果在程序末尾添加如下語句:

<pre class="python sh_python snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

1.  print(funB)

</pre>

其輸出結(jié)果為:

裝飾器函數(shù)的返回值

顯然,被“@函數(shù)”修飾的函數(shù)不再是原來的函數(shù),而是被替換成一個新的東西(取決于裝飾器的返回值),即如果裝飾器函數(shù)的返回值為普通變量,那么被修飾的函數(shù)名就變成了變量名;同樣,如果裝飾器返回的是一個函數(shù)的名稱,那么被修飾的函數(shù)名依然表示一個函數(shù)。

實際上,所謂函數(shù)裝飾器,就是通過裝飾器函數(shù),在不修改原函數(shù)的前提下,來對函數(shù)的功能進(jìn)行合理的擴(kuò)充。

帶參數(shù)的函數(shù)裝飾器

在分析 funA() 函數(shù)裝飾器和 funB() 函數(shù)的關(guān)系時,細(xì)心的讀者可能會發(fā)現(xiàn)一個問題,即當(dāng) funB() 函數(shù)無參數(shù)時,可以直接將 funB 作為 funA() 的參數(shù)傳入。但是,如果被修飾的函數(shù)本身帶有參數(shù),那應(yīng)該如何傳值呢?

比較簡單的解決方法就是在函數(shù)裝飾器中嵌套一個函數(shù),該函數(shù)帶有的參數(shù)個數(shù)和被裝飾器修飾的函數(shù)相同。例如:

<pre class="python sh_python snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

1.  def funA(fn):
2.  # 定義一個嵌套函數(shù)
3.  def say(arc):
4.  print("Python教程:",arc)
5.  return say

7.  @funA
8.  def funB(arc):
9.  print("funB():", a)
10.  funB("http://c.biancheng.net/python")

</pre>

程序執(zhí)行結(jié)果為:

Python教程: http://c.biancheng.net/python

這里有必要給讀者分析一下這個程序,其實,它和如下程序是等價的:

<pre class="python sh_python snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

1.  def funA(fn):
2.  # 定義一個嵌套函數(shù)
3.  def say(arc):
4.  print("Python教程:",arc)
5.  return say

7.  def funB(arc):
8.  print("funB():", a)

10.  funB = funA(funB)
11.  funB("http://c.biancheng.net/python")

</pre>

如果運(yùn)行此程序會發(fā)現(xiàn),它的輸出結(jié)果和上面程序相同。

顯然,通過 funB() 函數(shù)被裝飾器 funA() 修飾,funB 就被賦值為 say。這意味著,雖然我們在程序顯式調(diào)用的是 funB() 函數(shù),但其實執(zhí)行的是裝飾器嵌套的 say() 函數(shù)。

但還有一個問題需要解決,即如果當(dāng)前程序中,有多個(≥ 2)函數(shù)被同一個裝飾器函數(shù)修飾,這些函數(shù)帶有的參數(shù)個數(shù)并不相等,怎么辦呢?

最簡單的解決方式是用 *args 和 *kwargs 作為裝飾器內(nèi)部嵌套函數(shù)的參數(shù),args 和 **kwargs 表示接受任意數(shù)量和類型的參數(shù)。舉個例子:

<pre class="python sh_python snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

1.  def funA(fn):
2.  # 定義一個嵌套函數(shù)
3.  def say(*args,**kwargs):
4.  fn(*args,**kwargs)
5.  return say

7.  @funA
8.  def funB(arc):
9.  print("C語言中文網(wǎng):",arc)

11.  @funA
12.  def other_funB(name,arc):
13.  print(name,arc)
14.  funB("http://c.biancheng.net")
15.  other_funB("Python教程:","http://c.biancheng.net/python")

</pre>

運(yùn)行結(jié)果為:

C語言中文網(wǎng): http://c.biancheng.net
Python教程: http://c.biancheng.net/python

函數(shù)裝飾器可以嵌套

上面示例中,都是使用一個裝飾器的情況,但實際上,Python 也支持多個裝飾器,比如:

<pre class="python sh_python snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

1.  @funA
2.  @funB
3.  @funC
4.  def fun():
5.  #...

</pre>

上面程序的執(zhí)行順序是里到外,所以它等效于下面這行代碼:

fun = funA( funB ( funC (fun) ) )

這里不再給出具體實例,有興趣的讀者可自行編寫程序進(jìn)行測試。
vf

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容