Python的裝飾器是面試的常客,因爲其寫法複雜多變,經常忘記什麼地方應該寫哪種參數,新手學習起來也經常一頭霧水,不怕不怕,看了這一篇你對裝飾器的各種用法就全明白了。廢話不多說,直接進入主題!
不帶參數的函數,不帶參數的裝飾器
我們先來寫一個簡單的裝飾器,實現將函數運行前後的情況記錄下來。
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 相當於單獨的一個語句:
dec1(f1)
那我們來正式運行一下 f1 吧:
f1(1)
輸出如下確實達到了預期的效果:
before run
call f1
after run
不帶參數的函數,帶空參數的裝飾器
後面我們還想要給裝飾器加上參數呢,先試試用這個方式調用會發生什麼情況:
@dec1()
輸出了錯誤:
在學習過程中有什麼不懂得可以加我的
python學習扣扣qun,784758214
羣裏有不錯的學習視頻教程、開發工具與電子書籍。
與你分享python企業當下人才需求及怎麼從零基礎學習好python,和學習什麼內容
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 了呢!呃,再仔細看看,這寫法好像有些不一樣啊:
- dec8_c 的 init 帶的是裝飾器的參數,可是 dec9_c 帶的是裝飾器函數自己!
- 所以實際調用的函數名也可以在 init 中傳給它了哦!
- 而且 call 函數也簡潔了很多,看來有沒有參數真的有很大區別呢!
這裏先做個總結,裝飾器使用函數名形式(不帶括號)和使用函數調用形式(帶括號和參數)在實現上是不同的,因爲前者是函數本身,而後者是從裝飾器函數中返回的函數。這也是 f2 相比 f1 缺少了記錄 after run 的原因,因爲 dec1 直接調用了 f2 ,而 dec2 先運行得到函數,再把函數返回去調用 f2 。用裝飾器類就可以解決這個問題,因爲它是對 call 的調用,只需要自己定義一下就可以了。
上面的 f9 要寫兩個函數,能不能寫得和 f1 一樣簡潔?當然是可以的,使用 new 大法:
在學習過程中有什麼不懂得可以加我的
python學習扣扣qun,784758214
羣裏有不錯的學習視頻教程、開發工具與電子書籍。
與你分享python企業當下人才需求及怎麼從零基礎學習好python,和學習什麼內容
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