python3 裝飾器全解

whyaza發表於2018-08-20

本章結構:

1.理解裝飾器的前提準備

2.裝飾器:無參/帶參的被裝飾函式,無參/帶參的裝飾函式

3.裝飾器的缺點

4.python3的內建裝飾器

5.本文參考

 

理解裝飾器的前提:1.所有東西都是物件(函式可以當做物件傳遞) 2.閉包

閉包的概念:
1)函式巢狀
2)內部函式使用外部函式的變數
3)外部函式的返回值為內部函式

下面寫一個最為簡單的閉包的例子:

1 def test(name):
2     def test_in():
3         print(name)
4     return test_in
5 
6 func = test(`whyz`)
7 func()

裝飾器的原型:

 1 import time
 2 def showtime(func):
 3     def wrapper():
 4         start_time = time.time()
 5         func()
 6         end_time = time.time()
 7         print(`spend is {}`.format(end_time - start_time))
 8 
 9     return wrapper
10 
11 def foo():
12     print(`foo..`)
13     time.sleep(3)
14 
15 foo = showtime(foo)
16 foo()

不帶引數的裝飾器:(裝飾器,被裝飾函式都不帶引數)

 1 import time
 2 def showtime(func):
 3     def wrapper():
 4         start_time = time.time()
 5         func()
 6         end_time = time.time()
 7         print(`spend is {}`.format(end_time - start_time))
 8 
 9     return wrapper
10 
11 @showtime  #foo = showtime(foo)
12 def foo():
13     print(`foo..`)
14     time.sleep(3)
15 
16 @showtime #doo = showtime(doo)
17 def doo():
18     print(`doo..`)
19     time.sleep(2)
20 
21 foo()
22 doo()

帶引數的被裝飾的函式

 1 import time
 2 def showtime(func):
 3     def wrapper(a, b):
 4         start_time = time.time()
 5         func(a,b)
 6         end_time = time.time()
 7         print(`spend is {}`.format(end_time - start_time))
 8 
 9     return wrapper
10 
11 @showtime #add = showtime(add)
12 def add(a, b):
13     print(a+b)
14     time.sleep(1)
15 
16 @showtime #sub = showtime(sub)
17 def sub(a,b):
18     print(a-b)
19     time.sleep(1)
20 
21 add(5,4)
22 sub(3,2)

帶引數的裝飾器(裝飾函式),

實際是對原有裝飾器的一個函式的封裝,並返回一個裝飾器(一個含有引數的閉包函式),
當使用@time_logger(3)呼叫的時候,Python能發現這一層封裝,並將引數傳遞到裝飾器的環境去

 1 import time
 2 def time_logger(flag = 0):
 3     def showtime(func):
 4         def wrapper(a, b):
 5             start_time = time.time()
 6             func(a,b)
 7             end_time = time.time()
 8             print(`spend is {}`.format(end_time - start_time))
 9             
10             if flag:
11                 print(`將此操作保留至日誌`)
12 
13         return wrapper
14 
15     return showtime
16 
17 @time_logger(2)  #得到閉包函式showtime,add = showtime(add)
18 def add(a, b):
19     print(a+b)
20     time.sleep(1)
21 
22 add(3,4)

類裝飾器:一般依靠類內部的__call__方法

 1 import time
 2 class Foo(object):
 3     def __init__(self, func):
 4         self._func = func
 5 
 6     def __call__(self):
 7         start_time = time.time()
 8         self._func()
 9         end_time = time.time()
10         print(`spend is {}`.format(end_time - start_time))
11 
12 @Foo  #bar = Foo(bar)
13 def bar():
14     print(`bar..`)
15     time.sleep(2)
16 
17 bar()

使用裝飾器的缺點:

1.位置錯誤的程式碼->不要在裝飾器之外新增邏輯功能
2.不能裝飾@staticmethod 或者 @classmethod已經裝飾過的方法
3.裝飾器會對原函式的元資訊進行更改,比如函式的docstring,__name__,引數列表:

下面對裝飾器第第三個缺點進行剖析,

 1 import time
 2 def showtime(func):
 3     def wrapper():
 4         start_time = time.time()
 5         func()
 6         end_time = time.time()
 7         print(`spend is {}`.format(end_time - start_time))
 8 
 9     return wrapper
10 
11 @showtime  #foo = showtime(foo)
12 def foo():
13     print(`foo..`)
14     time.sleep(3)
15 
16 def doo():
17     print(`doo..`)
18     time.sleep(2)
19 
20 print(foo.__name__)
21 print(doo.__name__)

結果為:

wrapper
doo

由此可以看出,裝飾器會對原函式的元資訊進行更改,可以使用wraps,進行原函式資訊的新增

註解:wraps本身也是一個裝飾器,他能把函式的元資訊拷貝到裝飾器函式中使得裝飾器函式與原函式有一樣的元資訊

以下是一個wraps的例子:

 1 import time
 2 from functools import wraps
 3 def showtime(func):
 4 
 5     @wraps(func)    
 6     def wrapper():
 7         start_time = time.time()
 8         func()
 9         end_time = time.time()
10         print(`spend is {}`.format(end_time - start_time))
11 
12     return wrapper
13 
14 @showtime  #foo = showtime(foo)
15 def foo():
16     print(`foo..`)
17     time.sleep(3)
18 
19 def doo():
20     print(`doo..`)
21     time.sleep(2)
22 
23 print(foo.__name__)
24 print(doo.__name__)

結果為:

foo
doo

常用的內建裝飾器:1.staticmethod: 類似實現了靜態方法 注入以後,可以直接 : 類名.方法

2.property:經過property裝飾過的函式 不再是一個函式,而是一個property,類似實現get,set方法

1 @property
2 def width(self):
3 return self.__width
4 
5 @width.setter
6 def width(self, newWidth):
7 self.__width = newWidth

3.classmethod: 與staticmethod很相似,貌似就只有這一點區別:
第一個引數需要是表示自身類的 cls 引數,
可以來呼叫類的屬性,類的方法,例項化物件等。

 

 

本文參考:

1.https://www.cnblogs.com/vamei/archive/2013/02/16/2820212.html

2.https://www.cnblogs.com/wupeiqi/articles/4980620.html

3.https://www.cnblogs.com/yuanchenqi/articles/5830025.html

4.https://blog.csdn.net/mdzzname/article/details/78702440

 

相關文章