有時候我們希望給一個函式新增一些功能,但是又不想對原有函式進行修改,那麼這時候就用到了裝飾器或者說是裝飾模式。
例子:
# 1. 有一個求和函式sum
def sum(a, b):
return a + b
# 2. 如果想要知道這個函式的執行時間,如何實現?
# 實現一
import time
def sum(a, b):
return a + b
start = time.time()
sum(5, 6)
end = time.time()
print('sum函式的執行時間大約是:%f秒' % (end - start))
# 3. 如果還有一個求差函式sub,要知道這個函式的執行時間,如何實現?
'''
按照實現獲取sum函式執行時間的思路,完全可以照搬上面的程式碼,之前說過有重複
地方儘量封裝成函式。
'''
# 實現二
import time
def print_execution_time(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print('%s函式的執行時間大約是:%f秒' % (func.__name__, end - start))
return result
return wrapper
def sum(a, b):
return a + b
def sub(a, b):
return a - b
print_execution_time(sum)(10, 20)
print_execution_time(sub)(10, 20)
複製程式碼
這樣就實現了一個函式用於獲取某個函式的執行時間, 這種模式就是裝飾模式,有點像閉包,只不過傳遞的引數 換成了傳遞函式。
但是這樣做有點問題,就是原本函式的呼叫方式改變了。而且函式主要的目的也變得 很模糊,原函式主要是用於計算,改寫成這樣後就好像獲取執行時間才是主要目的。
Python提供了一個語法糖 - 裝飾器@。通過這個可以寫出更為簡潔的程式碼。例子:
# 上面例子使用Python提供的裝飾器改寫:
import time
def print_execution_time(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print('%s函式的執行時間大約是:%f秒' % (func.__name__, end - start))
return result
return wrapper
@print_execution_time # 新增
def sum(a, b):
return a + b
@print_execution_time # 新增
def sub(a, b):
return a - b
sum(10, 10)
sub(1, 3)
複製程式碼
結果:
用了裝飾器之後,呼叫和以前完全一樣。只是在定義函式的時候加上@裝飾函式名
這種語法,原本函式就獲得了裝飾函式中新增的功能。
上面例子中我們都用了func.__name__
,這句程式碼的意思是獲取函式的名字。但是用了上面裝飾器之後我們再看一下sum和sub函式的__name__
屬性:
print('sum的__name__是:%s' % (sum.__name__))
print('sub的__name__是:%s' % (sub.__name__))
複製程式碼
變了,全部變成wrpper了。這是因為使用@print_execution_time
的時候實際上等於
sum = print_execution_time(sum)
,得到結果就是wrapper函式。我們可以使用funcools模組中的wrap方法來改正這個問題:
import time
from functools import wraps # 新增
def print_execution_time(func):
@wraps(func) # 新增
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print('%s函式的執行時間大約是:%f秒' % (func.__name__, end - start))
return result
return wrapper
@print_execution_time
def sum(a, b):
return a + b
@print_execution_time
def sub(a, b):
return a - b
sum(10, 10)
sub(1, 3)
print('sum的__name__是:%s' % (sum.__name__)) # sum的__name__是:sum
print('sub的__name__是:%s' % (sub.__name__)) # sub的__name__是:sub
複製程式碼
裝飾器本身也是可以帶引數的,帶引數的裝飾器寫法如下:
import time
from functools import wraps
def print_execution_time(text):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print('%s函式的執行時間大約是:%f秒' % (func.__name__, end - start))
print(text)
return result
return wrapper
return decorator
@print_execution_time('今天天氣不錯')
def sum(a, b):
return a + b
@print_execution_time('今天天氣不好')
def sub(a, b):
return a - b
sum(10, 10)
sub(1, 3)
print('sum的__name__是:%s' % (sum.__name__))
print('sub的__name__是:%s' % (sub.__name__))
複製程式碼
結果:
寫法就是在原有的裝飾器上再包裹一層函式。
@print_execution_time('今天天氣不錯')
相當於
sum = print_execution_time('今天天氣不錯')(sum)
總結下兩種寫法:
- 不帶引數的裝飾器
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **wkargs):
# 寫新增功能
result = func(*args, **wkargs)
return result
return wrapper
複製程式碼
- 帶引數的裝飾器
from functools import wraps
from functools import wraps
def decorator_wrapper(*args, **wkargs):
def decorator(func):
@wraps(func)
def wrapper(*sub_args, **sub_wkargs):
# 寫新增功能
result = func(*sub_args, **sub_wkargs)
return result
return wrapper
return decorator
複製程式碼