前面章節(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 步操作:
- 將 B 作為參數(shù)傳給 A() 函數(shù);
- 將 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