裝飾器

裝飾器

1、裝飾器的引入

有一個需求,一個加法函數,想要加強它的功能,可以輸出被調用過以及調用的參數信息,這種需求應該如何解決呢?app

1 # 加法函數 add 的定義
2 def add(x, y):
3     return x+y
4 # 加強它的功能,輸出被調用過以及調用的參數信息
5 def add(x, y):
6     print("call add, x = {}, y = {}".format(x, y))
7     return x+y

思考:功能是加強了,可是這種直接在原來只計算值的函數中增長屬於非業務功能的代碼,這種方式好嗎?確定是很差的,缺點以下:函數

  1. 首先打印語句的耦合性過高了spa

  2. 加法函數屬於業務的邏輯,輸出信息屬於非業務功能的代碼,不該該直接放在該函數中。code

代碼改進,從新定義一個函數,在這個函數中來作輸出信息的功能:orm

 1 def add(x, y):
 2     return x+y
 3  4 def message_show(fn):
 5     print("begin...call add")
 6     result = fn(4, 5)
 7     print("end...")
 8     return result
 9 10 print(message_show(add))

思考:這種方式作到了業務功能的分離,可是 add 函數的傳參是一個問題,這種方式很不靈活,繼續改進。blog

代碼改進,解決參數的問題:it

 1 def add(x, y):
 2     return x+y
 3  4 def message_show(fn, *args):
 5     print(*args)
 6     print("begin...call add")
 7     result = fn(*args)
 8     print("end...{} + {}".format(*args))
 9     return result
10 11 print(message_show(add, 4, 5))

至此,成功作到了新增業務功能的分離,能夠看到並無在原來的 add 函數中加入任何的代碼,從而加強了它顯示信息的功能。io

2、裝飾器的改造

(一)柯里化

要寫出裝飾器,首先要對函數進行柯里化。柯里化的過程以下代碼:ast

 1 def add(x, y):
 2     return x+y
 3  4 def message_show(fn):
 5     def wrapper(*args):
 6         print(*args)
 7         print("begin...call add")
 8         result = fn(*args)
 9         print("end...{} + {}".format(*args))
10         return result
11     return wrapper
12 13 print(message_show(add)(4, 5))

思考:message_show(add)(4, 5)這種調用方式能不能改形成add(4, 5),這種調用方法是否是就看不出調用過 message_show 函數了,而是直接調用的 add 函數,繼續改造。function

(二)裝飾器定義

代碼改造,柯里化以後,就可使用裝飾器的語法糖:

 1 def message_show(fn):
 2     def wrapper(*args):
 3         print(*args)
 4         print("begin...call add")
 5         result = fn(*args)
 6         print("end...{} + {}".format(*args))
 7         return result
 8     return wrapper
 9 10 @message_show  # 等效於 add = message_show(add)
11 def add(x, y):
12     return x+y
13 14 print(add(4, 5))

注意:裝飾函數的代碼,必需要定義在被裝飾函數的代碼以前。

至此,一個簡單的裝飾器就出來了。

3、無參裝飾器

(一)無參裝飾器的理解

  • 裝飾器自己就是一個函數,並且是一個高階函數

  • 它的形參是一個函數,ps:要被裝飾的函數

  • 它的返回值是一個函數

  • 可使用 @function_name 的方式調用

(二)裝飾器的例子

 1 def fn_strengthen(fn):
 2     def wrapper(*args, **kwargs):
 3         print("========start=======")
 4         print("args = {}, kwargs = {}".format(args, kwargs))
 5         result = fn(*args, **kwargs)
 6         print(result)
 7         print("========end=========")
 8         return result
 9     return wrapper
10 11 @fn_strengthen  # add = fn_strengthen(add)
12 def add(x, y):
13     print("==========call add=============")
14     return x + y
15 16 add(4, y=5)

(三)裝飾器的反作用

 1 def fn_strengthen(fn):
 2     def wrapper(*args, **kwargs):
 3         print("========start=======")
 4         print("args = {}, kwargs = {}".format(args, kwargs))
 5         result = fn(*args, **kwargs)
 6         print(result)
 7  8         print("========end=========")
 9         return result
10     return wrapper
11 12 @fn_strengthen  # add = fn_strengthen(add)
13 def add(x, y):
14     """This is a function to add"""
15     print("==========call add=============")
16     return x + y
17 18 print("name = {}, doc = {}".format(add.__name__, add.__doc__))  # name = wrapper, doc = None

注意:能夠看出被裝飾函數的屬性都被替換了,這種問題應該如何解決?

代碼改進,再定義一個函數,用來記錄被裝飾函數的屬性:

 1 def fn_strengthen(fn):
 2     def wrapper(*args, **kwargs):
 3         print("========start=======")
 4         print("args = {}, kwargs = {}".format(args, kwargs))
 5         result = fn(*args, **kwargs)
 6         print(result)
 7  8         print("========end=========")
 9         return result
10     add_property(fn, wrapper)
11     return wrapper
12 13 def add_property(fn, wrapper):
14     wrapper.__name__ = fn.__name__
15     wrapper.__doc__ = fn.__doc__
16 17 @fn_strengthen  # add = fn_strengthen(add)
18 def add(x, y):
19     """This is a function to add"""
20     print("==========call add=============")
21     return x + y
22 23 print("name = {}, doc = {}".format(add.__name__, add.__doc__))

上面的方法解決了屬性被覆蓋的問題,考慮到add_property這個函數的通用性,能夠將這個複製屬性的函數也改形成裝飾器函數,只不過是帶參的裝飾器,代碼改造以下:

 1 def add_property(fn):
 2     def _copy(wrapper):
 3         wrapper.__name__ = fn.__name__
 4         wrapper.__doc__ = fn.__doc__
 5         return wrapper
 6     return _copy
 7  8 def fn_strengthen(fn):
 9     @add_property(fn)  # wrapper = add_property(fn)(wrapper)
10     def wrapper(*args, **kwargs):
11         print("========start=======")
12         print("args = {}, kwargs = {}".format(args, kwargs))
13         result = fn(*args, **kwargs)
14         print(result)
15 16         print("========end=========")
17         return result
18     return wrapper
19 20 @fn_strengthen  # add = fn_strengthen(add)
21 def add(x, y):
22     """This is a function to add"""
23     print("==========call add=============")
24     return x + y
25 26 print("name = {}, doc = {}".format(add.__name__, add.__doc__))

4、帶參裝飾器

(一)帶參裝飾器的引入

有一個需求,獲取函數的執行時長,對時長超過閾值的函數記錄一下:

 1 import datetime, time
 2  3 def add_property(fn):
 4     def _copy(wrapper):
 5         wrapper.__name__ = fn.__name__
 6         wrapper.__doc__ = fn.__doc__
 7         return wrapper
 8     return _copy
 9 10 def time_detection(timeout):
11     def fn_strengthen(fn):
12         @add_property(fn)  # wrapper = add_property(fn)(wrapper)
13         def wrapper(*args, **kwargs):
14             print("========start=======")
15             start = datetime.datetime.now()
16             result = fn(*args, **kwargs)
17             print(result)
18             delta = (datetime.datetime.now() - start).total_seconds()
19             print("so slow") if delta > timeout else print("so fast")
20             print("========end=========")
21             return result
22         return wrapper
23     return fn_strengthen
24 25 @time_detection(5)  # add = time_detection(5)(add)
26 def add(x, y):
27     """This is a function to add"""
28     print("==========call add=============")
29     time.sleep(6)
30     return x + y
31 32 add(4, 5)

(二)帶參裝飾器的理解

  • 帶參裝飾器自己是一個函數,並且是一個高階函數

  • 它的形參能夠是一個函數,返回值是一個不帶參數的裝飾器函數

  • 使用 @function_name(參數) 方式調用

  • 能夠看作是在裝飾器的外層又加了一層函數

(三)帶參裝飾器的例子

將上一個例子中的記錄時長的功能提取出來,能不能定義成一個參數來靈活的控制輸出,代碼改造以下:

 1 import datetime, time
 2  3 def add_property(fn):
 4     def _copy(wrapper):
 5         wrapper.__name__ = fn.__name__
 6         wrapper.__doc__ = fn.__doc__
 7         return wrapper
 8     return _copy
 9 10 def time_detection(timeout, func=lambda name, delta:print("{} spend {}s".format(name, delta))):
11     def fn_strengthen(fn):
12         @add_property(fn)  # wrapper = add_property(fn)(wrapper)
13         def wrapper(*args, **kwargs):
14             print("========start=======")
15             start = datetime.datetime.now()
16             result = fn(*args, **kwargs)
17             print(result)
18             delta = (datetime.datetime.now() - start).total_seconds()
19             if delta > timeout:
20                 func(fn.__name__, delta)
21             print("========end=========")
22             return result
23         return wrapper
24     return fn_strengthen
25 26 @time_detection(5)  # add = time_detection(5)(add)
27 def add(x, y):
28     """This is a function to add"""
29     print("==========call add=============")
30     time.sleep(6)
31     return x + y
32 33 add(4, 5)

5、functools模塊

這個模塊就省了本身從新寫複製屬性的那個函數,functools.update_wrapper(包裝函數, 原函數)使用這個模塊來改造前面獲取原函數屬性的函數:

 1 import datetime, time, functools
 2  3 def time_detection(timeout, func=lambda name, delta:print("{} spend {}s".format(name, delta))):
 4     def fn_strengthen(fn):
 5         def wrapper(*args, **kwargs):
 6             print("========start=======")
 7             start = datetime.datetime.now()
 8             result = fn(*args, **kwargs)
 9             print(result)
10             delta = (datetime.datetime.now() - start).total_seconds()
11             if delta > timeout:
12                 func(fn.__name__, delta)
13             print("========end=========")
14             return result
15         return functools.update_wrapper(wrapper, fn)
16     return fn_strengthen
17 18 @time_detection(5)  # add = time_detection(5)(add)
19 def add(x, y):
20     """This is a function to add"""
21     print("==========call add=============")
22     time.sleep(6)
23     return x + y
24 25 add(5, 6), add.__name__, add.__doc__

還可使用這個模塊的裝飾器的方法來複制屬性,@functools.wraps(原函數)

 1 import datetime, time, functools
 2 
 3 def time_detection(timeout, func=lambda name, delta:print("{} spend {}s".format(name, delta))):
 4     def fn_strengthen(fn):
 5         @functools.wraps(fn)
 6         def wrapper(*args, **kwargs):
 7             print("========start=======")
 8             start = datetime.datetime.now()
 9             result = fn(*args, **kwargs)
10             print(result)
11             delta = (datetime.datetime.now() - start).total_seconds()
12             if delta > timeout:
13                 func(fn.__name__, delta)
14             print("========end=========")
15             return result
16         return wrapper
17     return fn_strengthen
18 
19 @time_detection(5)  # add = time_detection(5)(add)
20 def add(x, y):
21     """This is a function to add"""
22     print("==========call add=============")
23     time.sleep(6)
24     return x + y
25 
26 print(add(5, 6), add.__name__, add.__doc__)
相關文章
相關標籤/搜索