深圳幻海软件技术有限公司 欢迎您!

五个真正方便的 Python 装饰器,用于分析和调试Python代码

2023-02-28

装饰器的美妙之处在于它们非常易于应用,为你的代码提供了许多额外的功能。在本文中,我将介绍5个方便的装饰器,你可以轻松地将它们应用于调试代码时遇到的实际问题。本文的目的是为你提供一些现成的装饰器,并启发你想出一些方便的通用装饰器。在我们开始之前:你知道你也可以让装饰器跟踪状态吗?示例:计算调用函数的次

装饰器的美妙之处在于它们非常易于应用,为你的代码提供了许多额外的功能。在本文中,我将介绍 5 个方便的装饰器,你可以轻松地将它们应用于调试代码时遇到的实际问题。

本文的目的是为你提供一些现成的装饰器,并启发你想出一些方便的通用装饰器。

在我们开始之前:你知道你也可以让装饰器跟踪状态吗?示例:计算调用函数的次数,以便你可以对其进行速率限制。请务必阅读: 以了解装饰器的工作原理、如何应用它们以及何时使用装饰器。

在本文中,我将通过以下5装饰器来探索装饰器。

  1. 计时你的功能
  2. 性能检查
  3. 中继器
  4. 在执行之前询问你是否确定
  5. 将你的功能包装在 try-catch 中

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

请注意,我们的装饰器本身是用@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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

现在我们调用函数看看输出:

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

我们还是用计算素数的函数上使用这个装饰器:

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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

我们调用素数函数来看看输出:

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(iteratinotallow=2)
def sayhello():
  print("hello")
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

现在调用 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()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

我们能够在装饰器上设置提示消息。当我们调用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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

现在,当我们调用trycatchExample(9, 3)函数时返回3.0。如果我们调用trycatchExample(9, 0)(除以 0),它会正确返回以下内容Exception in trycatchExample: division by zero

我建议仅将此装饰器用于调试代码,并更准确地捕获函数中的错误。

结论

通过这篇文章,我希望能够提供更多关于装饰器带来的优势的信息。如果我启发了你,请分享你自己的一些方便的装饰器。

本文转载自微信公众号「树哥会编程」,可以通过以下二维码关注。转载本文请联系树哥会编程公众号。