有一個需求,一個加法函數,想要加強它的功能,可以輸出被調用過以及調用的參數信息,這種需求應該如何解決呢?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
思考:功能是加強了,可是這種直接在原來只計算值的函數中增長屬於非業務功能的代碼,這種方式好嗎?確定是很差的,缺點以下:函數
首先打印語句的耦合性過高了spa
加法函數屬於業務的邏輯,輸出信息屬於非業務功能的代碼,不該該直接放在該函數中。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
要寫出裝飾器,首先要對函數進行柯里化。柯里化的過程以下代碼: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))
注意:裝飾函數的代碼,必需要定義在被裝飾函數的代碼以前。
至此,一個簡單的裝飾器就出來了。
裝飾器自己就是一個函數,並且是一個高階函數
它的形參是一個函數,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__))
有一個需求,獲取函數的執行時長,對時長超過閾值的函數記錄一下:
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)
這個模塊就省了本身從新寫複製屬性的那個函數,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__)