天天看點

5 個真正友善的 Python 裝飾器,用于分析/調試代碼

作者:喜歡編碼的社畜
5 個真正友善的 Python 裝飾器,用于分析/調試代碼

裝飾器的美妙之處在于它們非常易于應用,為你的代碼提供了許多額外的功能。在本文中,我将介紹 5 個友善的裝飾器,你可以輕松地将它們應用于調試代碼時遇到的實際問題。

  1. 計時你的功能
  2. 性能檢查
  3. 中繼器
  4. 在執行之前詢問你是否确定
  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)           

現在我們調用函數看看輸出:

5 個真正友善的 Python 裝飾器,用于分析/調試代碼

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)           

我們調用素數函數來看看輸出:

5 個真正友善的 Python 裝飾器,用于分析/調試代碼

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() 将産生以下輸出,這個裝飾器可以很好地用于執行幾次,例如測試函數的性能

5 個真正友善的 Python 裝飾器,用于分析/調試代碼

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 個真正友善的 Python 裝飾器,用于分析/調試代碼

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           
5 個真正友善的 Python 裝飾器,用于分析/調試代碼

我建議僅将此裝飾器用于調試代碼,并更準确地捕獲函數中的錯誤。

結論

通過這篇文章,我希望能夠提供更多關于裝飾器帶來的優勢的資訊。如果我啟發了你,請分享你自己的一些友善的裝飾器。

如果你有建議/澄清,請發表評論,以便我改進這篇文章。