♚python
做者:吉星高照, 網易遊戲資深開發工程師,主要工做方向爲網易遊戲 CDN 自動化平臺的設計和開發,腦洞比較奇特,喜歡在各類非主流的領域研究製做各類不走尋常路的東西。面試
!函數
Python的裝飾器是面試的常客,由於其寫法複雜多變,常常忘記什麼地方應該寫哪一種參數,新手學習起來也常常一頭霧水,不怕不怕,看了這一篇你對裝飾器的各類用法就全明白了。廢話很少說,直接進入主題!學習
咱們先來寫一個簡單的裝飾器,實現將函數運行先後的狀況記錄下來。ui
def dec1(func): print(func) def _wrap(): print('before run') r = func() print('after run') return r return _wrap @dec1 def f1(): print('call f1')
上面只是定義了兩個函數,運行後發現居然有輸出:設計
<function f1 at 0x7fa1585f8488>
仔細看看,原來是第一個 print 語句的輸出。這說明裝飾的函數尚未實際運行的時候,裝飾器就運行過了,由於 @dec1 至關於單獨的一個語句:blog
dec1(f1)
那咱們來正式運行一下 f1 吧:遊戲
f1(1)
輸出以下確實達到了預期的效果:ip
before run call f1 after run
後面咱們還想要給裝飾器加上參數呢,先試試用這個方式調用會發生什麼狀況:開發
@dec1()
輸出了錯誤:
Traceback (most recent call last) <ipython-input-268-01cf93cf6907> in <module> 8 return _wrap 9 ---> 10 @dec1() 11 def f1(): 12 print('call f1') TypeError: dec1() missing 1 required positional argument: 'func'
它說 dec1 須要接受 func 這個參數才行,那咱們改改,做爲 f2 函數吧:
def dec2(): def _wrap(func): print(func) print('before run') return func return _wrap @dec2() def f2(): print('call f2') f2()
這下能夠了:
<function f2 at 0x7fa1585af2f0> before run call f2
但是這個結構和原來有點不一樣了,並且, after run 要寫在哪裏呢?很愁人地又改了一版,把它叫作 f2x 吧,對比 dec1 ,又多了一層函數 dec2x_w ,開始有點暈:
def dec2x_w(): def dec2x(func): print(func) def _wrap(): print('before run') r = func() print('after run') return r return _wrap return dec2x @dec2x_w() def f2x(): print('call f2x') f2x()
運行一下看看,確實是想要的:
<function f2x at 0x7fa1585af950> before run call f2x after run
後面咱們就不加 before/after run 了。
函數 f2x 想要接受參數呢?咱們把它叫作 a 吧,比較簡單,不就是 _wrap 的參數嗎,加上就是了,並且又回到了只有兩層函數就能夠實現了:
def dec3(func): print(func) def _wrap(param): print(param) r = func(param) return r return _wrap @dec3 def f3(a): print('call f3', a) f3(1)
很爭氣地輸出告終果:
<function f3 at 0x7fa158719620> 1 call f3 1
下面咱們實現一個裝飾器,傳入一個整數,和函數傳入的參數作個加法:
def dec4_w(d_param): print(d_param) def dec4(func): print(func) def _wrap(param): print(param) r = func(param + d_param) return r return _wrap return dec4 @dec4_w(2) def f4(a): print('call f4', a) f4(1)
輸出 1+2=3 :
2 <function f4 at 0x7fa1585af598> 1 call f4 3
從調用的裝飾器往裏看,注意這三層函數的形參,第一層是裝飾器的參數,第二層是函數,第三層是函數的參數,頗有規律的排列,先記一下這個規律(要考的)。帶兩個參數的也是同樣的,接着寫就能夠了:
def dec5_w(d_param_1, d_param_2): print(d_param_1, d_param_2) def dec5(func): print(func) def _wrap(param): print(param) r = func(param + d_param_1 + d_param_2) return r return _wrap return dec5 @dec5_w(2, 3) def f5(a): print('call f5', a) f5(1)
輸出 1+2+3=6 :
2 3 <function f5 at 0x7fa1586237b8> 1 call f5 6
若是用不定數量的位置參數,就用 *args 做爲形參吧:
def dec6_w(*args): d_param_1, d_param_2, = args print(d_param_1, d_param_2) def dec6(func): print(func) def _wrap(*args): param = args[0] print(param) r = func(param + d_param_1 + d_param_2) return r return _wrap return dec6 @dec6_w(2, 3) def f6(a): print('call f6', a) f6(1) print(f6.__name__)
順便輸出了一下 f6 的函數名:
2 3 <function f6 at 0x7fa1586236a8> 1 call f6 6 _wrap
咦!怎麼肥四!!! f6 怎麼是裏面那個 _wrap 的名字呢?不怕不怕, functools 提供了一個 wraps 裝飾器專治各類不服(在裝飾器裏面放上另外一個裝飾器):
from functools import wraps def dec7_w(*args): d_param_1, d_param_2, = args print(d_param_1, d_param_2) def dec7(func): print(func) @wraps(func) def _wrap(*args): param = args[0] print(param) r = func(param + d_param_1 + d_param_2) return r return _wrap return dec7 @dec7_w(2, 3) def f7(a): print('call f7', a) f7(1) print(f7.__name__)
這下正常輸出 f7 了:
2 3 <function f7 at 0x7fa1585f8510> 1 call f7 6 f7
用函數作裝飾器侷限性太多了,用相同的調用方法,把函數 f7 改爲類怎麼樣?emmm…改造工程有點大,直接看當作品吧:
from functools import wraps class dec8_c: def __init__(self, *args): self.d_param_1, self.d_param_2, = args print(self.d_param_1, self.d_param_2) def __call__(self, func): print(func) @wraps(func) def _wrap(param): print(param) r = func(param + self.d_param_1 + self.d_param_2) return r return _wrap @dec8_c(2, 3) def f8(a): print('call f8', a) f8(1) print(f8.__name__)
看看是否是實現了同樣的效果:
2 3 <function f8 at 0x7fa1585f8048> 1 call f8 6 f8
雖然使用了 __call__ ,但這裏的 __init__ 不能省略(由於它須要知道參數個數),不然會出現這個錯誤:
Traceback (most recent call last) <ipython-input-276-1634a47057a2> in <module> 14 return dec8 15 ---> 16 @dec8_c(2, 3) 17 def f8(a): 18 print('call f8', a) TypeError: dec8_c() takes no arguments
同時還能夠發現, __call__ 只須要兩層函數了,去掉了第二層,直接把 _wrap 的函數體往上提了一層!
大概是吃飽了撐着,又想要實現一開始那個不帶參數的裝飾器了,那就繼續敲敲打打一番看看:
class dec9_c: def __init__(self, func): print(func) self.func = func self.__name__ = func.__name__ def __call__(self, param): print(param) func = self.func r = func(param) return r @dec9_c def f9(a): print('call f9', a) f9(1) print(f9.__name__)
趕快運行看看:
<function f9 at 0x7fa1585f8730> 1 call f9 1 f9
咦, f9 的函數名能夠直接打印,這下都不用 @wraps 了呢!呃,再仔細看看,這寫法好像有些不同啊:
這裏先作個總結,裝飾器使用函數名形式(不帶括號)和使用函數調用形式(帶括號和參數)在實現上是不一樣的,由於前者是函數自己,然後者是從裝飾器函數中返回的函數。這也是 f2 相比 f1 缺乏了記錄 after run 的緣由,由於 dec1 直接調用了 f2 ,而 dec2 先運行獲得函數,再把函數返回去調用 f2 。用裝飾器類就能夠解決這個問題,由於它是對 __call__ 的調用,只須要本身定義一下就能夠了。
上面的 f9 要寫兩個函數,能不能寫得和 f1 同樣簡潔?固然是能夠的,使用 __new__ 大法:
from functools import wraps class dec9x_c: def __new__(self, func): print(func) @wraps(func) def dec9x(param): print(param) r = func(param) return r return dec9x @dec9x_c def f9x(a): print('call f9x', a) f9x(1) print(f9x.__name__)
這樣就避開了函數調用,不用打call了(定義 __call__ 函數),快看它來了:
<function f9x at 0x7fa158623bf8> 1 call f9x 1 f9x