裝飾器的美妙之處在于它們非常易于應用,為你的代碼提供了許多額外的功能。在本文中,我将介紹 5 個友善的裝飾器,你可以輕松地将它們應用于調試代碼時遇到的實際問題。
- 計時你的功能
- 性能檢查
- 中繼器
- 在執行之前詢問你是否确定
- 将你的功能包裝在 try-catch 中
本文的目的是為你提供一些現成的裝飾器,并啟發你想出一些友善的通用裝飾器。
在我們開始之前: 你知道你也可以讓裝飾器跟蹤狀态嗎?示例:計算調用函數的次數,以便你可以對其進行速率限制。請務必閱讀: Python—編寫裝飾器的最佳實踐,以了解裝飾器的工作原理、如何應用它們以及何時使用裝飾器。在本文中,我将通過 6 個越來越複雜的步驟來探索裝飾器。
1.定時器
讓我們從簡單的開始;我們将從一個裝飾器開始,它列印出我們的函數運作所花費的時間。這是代碼:
from functools import wraps
import time
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
# Call the actual function
res = func(*args, **kwargs)
duration = time.perf_counter() - start
print(f'[{wrapper.__name__}] took {duration * 1000} ms')
return res
return wrapper
請注意,我們的裝飾器本身是用@wraps(func) 包裹的。這是為了確定我們傳遞我們的包裝函數。如果我們不這樣做wrapper.__name__,隻會列印 'wrapper' 而不是我們實際裝飾的函數。
我将在計算素數的函數上使用這個裝飾器:
from functools import wraps
import time
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
# Call the actual function
res = func(*args, **kwargs)
duration = time.perf_counter() - start
print(f'[{wrapper.__name__}] took {duration * 1000} ms')
return res
return wrapper
@timer
def isprime(number: int):
""" Checks whether a number is a prime number """
isprime = False
for i in range(2, number):
if ((number % i) == 0):
isprime = True
break
return isprime
if __name__ == "__main__":
isprime(number=155153)
現在我們調用函數看看輸出:
2. 性能檢查
定時我們的功能很有用,但我們想要更多資訊。除了持續時間之外,下面這個裝飾器還提供有關函數的資訊,包括名稱和文檔字元串,以及記憶體使用情況等性能資訊:
from functools import wraps
import time
def performance_check(func):
"""Measure performance of a function"""
@wraps(func)
def wrapper(*args, **kwargs):
tracemalloc.start()
start_time = time.perf_counter()
res = func(*args, **kwargs)
duration = time.perf_counter() - start_time
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"\nFunction: {func.__name__} ({func.__doc__})"
f"\nMemory usage: {current / 10**6:.6f} MB"
f"\nPeak memory usage: {peak / 10**6:.6f} MB"
f"\nDuration: {duration:.6f} sec"
f"\n{'-'*40}"
)
return res
return wrapper
我們還是用計算素數的函數上使用這個裝飾器:
from functools import wraps
import time,tracemalloc
def performance_check(func):
"""Measure performance of a function"""
@wraps(func)
def wrapper(*args, **kwargs):
tracemalloc.start()
start_time = time.perf_counter()
res = func(*args, **kwargs)
duration = time.perf_counter() - start_time
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"\nFunction: {func.__name__} ({func.__doc__})"
f"\nMemory usage: {current / 10**6:.6f} MB"
f"\nPeak memory usage: {peak / 10**6:.6f} MB"
f"\nDuration: {duration:.6f} sec"
f"\n{'-'*40}"
)
return res
return wrapper
@performance_check
def isprime(number: int):
""" Checks whether a number is a prime number """
isprime = False
for i in range(2, number):
if ((number % i) == 0):
isprime = True
break
return isprime
if __name__ == "__main__":
a = isprime(number=155153)
print(a)
我們調用素數函數來看看輸出:
3.中繼器
此裝飾器在調用時重複某個功能。這可以友善測試性能或壓力測試,例如
def repeater(iterations:int=1):
""" Repeats the decorated function [iterations] times """
def outer_wrapper(func):
def wrapper(*args, **kwargs):
res = None
for i in range(iterations):
res = func(*args, **kwargs)
return res
return wrapper
return outer_wrapper
我們使用一個列印hello的函數來測試一下,讓它執行兩次。
def repeater(iterations:int=1):
""" Repeats the decorated function [iterations] times """
def outer_wrapper(func):
def wrapper(*args, **kwargs):
res = None
for i in range(iterations):
res = func(*args, **kwargs)
return res
return wrapper
return outer_wrapper
@repeater(iterations=2)
def sayhello():
print("hello")
現在調用 sayhello() 将産生以下輸出,這個裝飾器可以很好地用于執行幾次,例如測試函數的性能
4. 在執行函數之前提示你是否繼續執行
這個裝飾器可以添加到需要很長時間才能完成或具有重大後果(如删除資料)的函數中。一旦你調用該函數,裝飾器就會確定你在調用之前确認你要執行該函數。否則它隻會傳回而不調用該函數。
def prompt_sure(prompt_text:str):
""" Shows prompt asking you whether you want to continue. Exits on anything but y(es) """
def outer_wrapper(func):
def wrapper(*args, **kwargs):
if (input(prompt_text).lower() != 'y'):
return
return func(*args, **kwargs)
return wrapper
return outer_wrapper
我們依然使用sayhello函數來示範該裝飾器的功能
def prompt_sure(prompt_text:str):
""" Shows prompt asking you whether you want to continue. Exits on anything but y(es) """
def outer_wrapper(func):
def wrapper(*args, **kwargs):
if (input(prompt_text).lower() != 'y'):
return
return func(*args, **kwargs)
return wrapper
return outer_wrapper
@prompt_sure('Sure? Press y to continue, press n to stop. ')
def sayhello():
print("hi")
if __name__ == "__main__":
sayhello()
我們能夠在裝飾器上設定提示消息。當我們調用sayhello()時,會看到
Sure? Press y to continue, press n to stop.
如果輸入 'y' 那麼我們将執行sayhello(),任何其他輸入(包括沒有輸入将阻止sayhello()執行)。
5. 裝飾器中的 TryCatch
這使用裝飾器将您的函數包裝在 try-except-block 中。優點是,隻需一行 Python 代碼,您的整個函數就可以免受異常的影響。這是代碼的樣子:
def trycatch(func):
""" Wraps the decorated function in a try-catch. If function fails print out the exception. """
@wraps(func)
def wrapper(*args, **kwargs):
try:
res = func(*args, **kwargs)
return res
except Exception as e:
print(f"Exception in {func.__name__}: {e}")
return wrapper
我們将在下面的函數中使用這個裝飾器
def trycatch(func):
""" Wraps the decorated function in a try-catch. If function fails print out the exception. """
@wraps(func)
def wrapper(*args, **kwargs):
try:
res = func(*args, **kwargs)
return res
except Exception as e:
print(f"Exception in {func.__name__}: {e}")
return wrapper
@trycatch
def trycatchExample(numA:float, numB:float):
return numA / numB
if __name__ == "__main__":
trycatchExample(9.3)
trycatchExample(9,0)
現在,當我們調用trycatchExample(9, 3)函數時傳回3.0。如果我們調用trycatchExample(9, 0)(除以 0),它會正确傳回以下内容
Exception in trycatchExample: division by zero
我建議僅将此裝飾器用于調試代碼,并更準确地捕獲函數中的錯誤。
結論
通過這篇文章,我希望能夠提供更多關于裝飾器帶來的優勢的資訊。如果我啟發了你,請分享你自己的一些友善的裝飾器。
如果你有建議/澄清,請發表評論,以便我改進這篇文章。