1. 程式人生 > 實用技巧 >Python學習筆記之裝飾器

Python學習筆記之裝飾器

裝飾器簡單來說,就是現有的物件,在不修改原始碼和呼叫方式的情況,對現有的物件新增新的功能,比如插入日誌,許可權校驗之類的。

為什麼要用裝飾器?

1、現在有這麼一個物件,我要給這個物件新增一個列印日誌的功能

1 def foo():
2     print("I'm foo function")

2、那麼可以這樣改

1 def foo():
2     print("I'm foo function")
3     print("logging")

但是如果有10個物件,是不是要在10處加上列印日誌的程式碼?那如果是50個,100個呢?這樣會有大量重複的程式碼出現

3、那還可以這樣改,把foo當作一個引數傳給日誌功能不就好了麼

1 def foo():
2     print("I'm foo function")
3 
4 def logging(func):
5     print("logging")
6     func()
7 
8 logging(foo)

這樣會有一個情況就是,修改了呼叫方式,假如這段程式碼已經在生產環境中運行了,而且有很多地方都引用這段程式碼,不可能把所有已經引用的地方全都修改了吧

這時候就需要用到裝飾器了,不用修改原來物件的程式碼和呼叫方式,新增新的功能,還可以避免寫重複的程式碼

函式可以賦值給變數

例如

1 def foo():
2     print("I'm foo function
") 3 4 f = foo 5 f() 6 7 #輸出結果 8 I'm foo function

函式名後面有小括號和沒有小括號的區別

 1 def foo():
 2     print("I'm foo function")
 3 
 4 print(foo)    #沒有小括號表示函式的記憶體地址
 5 print('---我---是---分---割---線---')
 6 print(foo())  #有小括號表示函式的執行結果
 7 
 8 #輸出結果
 9 <function foo at 0x0000026EE94C8F70>
10 ---我---是---分---割---線---
11
I'm foo function 12 None

為什麼要說這個函式可以賦值和有沒有小括號的問題呢,因為下面會用到這個

沒有引數的物件進行裝飾

1、還是這個例子,為其新增日誌功能

1 def foo():
2     print("I'm foo function")

2、下面看一下兩個例子

(1)語法糖的寫法

 1 def logging(func):  #1
 2     def deco():   #3
 3         print("logging")    #6
 4         func()   #7
 5     return deco   #4
 6 
 7 @logging  #2
 8 def foo():
 9     print("I'm foo function")   #8
10 
11 foo()   #5
12 
13 #輸出結果
14 logging
15 I'm foo function
(注:#1 #2 #3 ....#8 其表示的意思是在除錯模式下的執行順序)

(2)重新賦值的寫法

 1 def logging(func):    #1
 2     def deco():   #4
 3         print("logging")  #7
 4         func()   #8
 5     return deco   #5
 6 
 7 def foo():   #2
 8     print("I'm foo function")   #9
 9 
10 foo=logging(foo)   #3
11 foo()   #6
12 
13 #輸出結果
14 logging
15 I'm foo function
(注:#1 #2 #3 ....#9 其表示的意思是在除錯模式下的執行順序)

這兩個例子是等價關係,語法糖@logging的寫法,相當於隱式的做了foo=logging(foo)

logging(foo)返回的是deco這個函式的記憶體地址,被重新賦值給了foo這個函式名,所以現在foo=deco

當執行foo()的時候,其實就是deco()

需要注意的是foo和foo()這兩個的區別,foo是表示一個記憶體地址,而foo()表示的是執行結果

有引數的物件進行裝飾

 1 def logging(func):
 2     def deco(*args, **kwargs):   # *args表示可以接收任意個位置引數,**kwargs表示可以接收任意個關鍵字引數, 位置引數*args要放在關鍵字引數**kwargs的前面
 3         print("logging")
 4         func(*args, **kwargs)
 5     return deco
 6 
 7 @logging
 8 def foo():
 9     print("My name is foo function")
10 
11 @logging
12 def bar(a, b):
13     print("My name is %s %s"%(a, b))
14 
15 @logging
16 def ten(x, y):
17     print("My name is %s %s"%(x, y))
18 
19 
20 foo('foo')
21 print('---我---是---分---割---線---')
22 bar('bar', 'function')
23 print('---我---是---分---割---線---')
24 ten('ten', y='function')
25 
26 #輸出結果
27 logging
28 My name is foo function
29 ---我---是---分---割---線---
30 logging
31 My name is bar function
32 ---我---是---分---割---線---
33 logging
34 My name is ten function

可適用於不帶引數的物件、帶引數的物件,以及關鍵字引數的物件的裝飾

當然還有一些更高階的用法,比如裝飾器也帶引數的

可參考以下兩個文章:

https://www.cnblogs.com/arvin-feng/p/11108799.html

https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584

若以上內容表述有誤,歡迎各位大神指導一下。