裝飾器本質上就是一個python函數,他可以讓其他函數在不需要做任何代碼變動的前提下,增加額外的功能,裝飾器的返回值也是一個函數對象。 裝飾器的應用場景:比如插入日誌,性能測試,事務處理,緩存等等場景。 ...
【函數的有用信息】
例:
def login(user, pwd):
'''
功能:登錄調用
參數:分別有user和pwd,作用分別是用戶和密碼;
return: 返回值是登錄成功與否(True,False)
'''
print(user, pwd, "歡迎登錄")
print(login.__name__) #查看函數名字
print(login.__doc__) #查看函數註釋
login
功能:登錄調用
參數:分別有user和pwd,作用分別是用戶和密碼;
return: 返回值是登錄成功與否(True,False)
函數名.__name__ 這是查看函數名字的方法
函數名.__doc__ 這是查看函數註釋的方法
上面的兩個方法在做運維審計日誌時很有用,配合裝飾器使用可以記錄下不少信息。
不過要是用在被裝飾器裝飾過的函數時顯示的會變成是裝飾器的函數名和註釋,
所以需要用到wraps模塊去實現顯示的名字和註釋仍是被裝飾的原函數。
例:
from functools import wraps
# 函數工具庫的warps模塊來實現裝飾器函數名字仍是被裝飾的原函數名字
def deco(func):
@wraps(func) #加在最內層函數正上方
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@deco
def index():
'''首頁的註釋'''
print('from index')
print(index.__doc__)
print(index.__name__)
首頁的註釋
index
【裝飾器Decorator】
裝飾器本質上就是一個python函數,他可以讓其他函數在不需要做任何代碼變動的前提下,增加額外的功能,裝飾器的返回值也是一個函數對象。
裝飾器的應用場景:比如插入日誌,性能測試,事務處理,緩存等等場景。
裝飾器的語法糖:@緊接著裝飾器的函數名,放置在需被裝飾的函數上方。
因為@的形象很像一個甜甜的一個一個圈的棒棒糖,所以又被人們愛稱為語法糖。
相對直接生硬的將要被裝飾的函數名賦值成裝飾器的函數名這種方式來說靈活和優雅得多,
就算要改動也不致於太麻煩。
例:下麵是一個在調用函數時先進行列印格林威治時間的Decorator:
import time # 引入時間模塊
def wrapper(func):
'''
此裝飾器主要用於測試被裝飾函數的工作時間
'''
def inner(*args, **kwargs):
start_time = time.time()
ret = func(*args, **kwargs)
time.sleep(0.2322) # 模擬函數執行後經過的時間
end_time = time.time()
sec = end_time - start_time
print('此程式執行時間為%s' % round(sec, 5))
return ret # 相當於是返回func()
return inner
@wrapper # 語法糖裝飾上後,相當於是將下方的 hello = wrapper(hello)
def hello():
print('hero say hello')
hello()
# 相當於是執行wrapper(hello)(),就相當於是執行wrapper裡層的inner函數體。
# 所以會執行額外添加的功能,同時函數也正常調用了。
# 執行效果如下:
hero say hello
此程式執行時間為0.23301
上面例子是嵌套的裝飾器,它比較簡單。
因為我們在內層的inner函數中有設置動態參數(*args和**kwargs)
被裝飾的函數有設置參數的時候,也可以正常傳遞,如:
@wrapper # 語法糖裝飾上後,相當於是將下方的 hello = wrapper(hello)
def hello(name):
print('hero say hello %s' % name)
hello('tiele')
# 相當於是執行wrapper(hello)(),就相當於是執行wrapper裡層的inner函數體。
# 所以會執行額外添加的功能,同時函數也正常調用了。
返回:
hero say hello tiele
此程式執行時間為0.23301
但有一點要註意的
經過decorator裝飾之後的函數,它們的__name__已經從原來的'函數名'變成了'inner':
print(hello.__name__)
hero say hello tiele
此程式執行時間為0.23301
inner
因為直接用查看函數名字與註釋的方法去查看經過被裝飾器裝飾了的函數會得出的是錯誤的信息(返回的是裝飾器的名字與註釋),這不符合我們的需求,(雖然直接在inner函數體內列印func.__name__可以得出原函數名,但在全局環境下使用.__name__查看還是會出錯)所以還得用上python內置的functools.wraps去支持全局查看函數名時能看到正確的原函數名:
import time # 引入時間模塊
import functools # 引入函數工具模塊
def wrapper(func):
'''
此裝飾器主要用於測試被裝飾函數的工作時間
'''
# 加在最內層函數的正上方,因為要的是最內函數的名字
@functools.wraps(func)
def inner(*args, **kwargs):
start_time = time.time()
ret = func(*args, **kwargs)
time.sleep(0.2322) # 模擬函數執行後經過的時間
end_time = time.time()
sec = end_time - start_time
print('此程式執行時間為%s' % round(sec, 5))
return ret # 相當於是返回func()
return inner
@wrapper # 語法糖裝飾上後,相當於是將下方的 hello = wrapper(hello)
def hello(name):
print('hero say hello %s' % name)
hello('tiele')
# 相當於是執行wrapper(hello)(),就相當於是執行wrapper裡層的inner函數體。
# 所以會執行額外添加的功能,同時函數也正常調用了。
print('函數名為'+ hello.__name__)
hero say hello tiele
此程式執行時間為0.23301
函數名為hello
如上例,看起來像是在inner函數上方也裝了一個語法糖裝飾器一樣。
【帶參數的裝飾器】
函數中帶參數,我們是實現了。可是,有些情況需要裝飾器本身也帶上參數以便控制流程和裝飾器開關的,就很有必要再嵌套一層外面的函數去實現了。
例:(最外層這次參數內是動態參數,而不是接受被裝飾的函數名做參數了)
from functools import wraps
# 函數工具庫的warps模塊來實現裝飾器函數名字仍是被裝飾的原函數名字
import time
# 引進時間模塊
def timer(*args, **kwargs):
def wrapper(f):
@wraps(f)
# 加在最內層函數的正上方,因為要的是最內函數的名字
def inner(*args, **kwargs):
'''
此功能用於測試函數起始時間與結束時間
:param args:
:param kwargs:
:return:
'''
if flag:
StartTime = time.time()
ret = f(*args, **kwargs)
time.sleep(0.3)
EndTime = time.time()
sec = EndTime - StartTime
print('此程式執行效率%f' % sec)
else:
ret = f(*args, **kwargs)
return ret
return inner
return wrapper
flag = True
@timer()
def hello_word():
'''
向世界say hello
:return: None
'''
print('hello,word.hello,hero.')
hello_word()
print(hello_word.__name__)
print(hello_word.__doc__)
hello,word.hello,hero.
此程式執行效率0.300017
hello_word
向世界say hello
:return: None
flag為False,裝飾器的計算時間功能便不會執行。
def timer(flag):
# 帶flag參數,可以控制裝飾器的開關
def wrapper(f):
@wraps(f)
# 加在最內層函數的正上方,因為要的是最內函數的名字
def inner(*args, **kwargs):
'''
此功能用於測試函數起始時間與結束時間
:param args:
:param kwargs:
:return:
'''
if flag:
StartTime = time.time()
ret = f(*args, **kwargs)
time.sleep(0.3)
EndTime = time.time()
sec = EndTime - StartTime
print('此程式執行效率%f' % sec)
else:
ret = f(*args, **kwargs)
return ret
return inner
return wrapper
@timer(False)
def hello_word():
'''
向世界say hello
:return: None
'''
print('hello,word.hello,hero.')
hello_word()
hello,word.hello,hero.
上面例子中,當我們將flag的值改成False,便不會觸發裝飾器的額外功能。
【開放封閉原則】
1.對擴展是開放的
為什麼要對擴展開放呢?
任何一個程式,不可能在設計之初就已經想好了所有的功能並且未來不做任何更新和修改。
所以必須允許代碼擴展、添加新功能。
2.對修改是封閉的
為什麼要對修改封閉呢?
寫的一個函數,很有可能已經交付給其他人使用了,如果這個時候對其進行了修改,很有可能影響其他已經在使用該函數的用戶。
裝飾器完美的遵循了這個開放封閉原則。
【多個裝飾器裝飾一個函數】
例:
def wrapper1(func):
def inner():
print('wrapper1 ,before func')
func()
print('wrapper1 ,after func')
return inner
def wrapper2(func):
def inner():
print('wrapper2 ,before func')
func()
print('wrapper2 ,after func')
return inner
@wrapper2
@wrapper1
def f():
print('in f')
f()
效果:
wrapper2 ,before func
wrapper1 ,before func
in f
wrapper1 ,after func
wrapper2 ,after func
規律就是先執行完原里函數前面的語句,再執行原函數,再到里函數後面的語句。
如圖:
end
2018-4-4