重要的 Python 装饰器

2024 年 08 月 29 日 | 阅读 9 分钟

在本教程中,我们将学习一些每个 Python 开发人员都应该知道的惊人 Python 装饰器。这些装饰器将帮助将代码提升到一个新的水平,并用更少的代码完成更多的事情。

众所周知,Python 装饰器是一个强大的工具,有助于编写可扩展和干净的代码。为了理解本教程,应该熟悉装饰器。如果您不熟悉装饰器,下面是装饰器的简要介绍。

在 Python 中,装饰器是一种特殊类型的函数,可用于修改另一个函数或类的行为。装饰器接收一个函数或类作为输入,对其执行一些修改,并返回修改后的函数或类。

装饰器由 @ 符号后跟装饰器函数的名称表示,并紧密放置在要装饰的函数或类之前。当调用被装饰的函数或类时,装饰器函数首先执行,其结果用作新的函数或类。

示例 -

输出

7

现在,我们将看到重要 Python 装饰器的列表。

1. @logger

logger 是一个函数,它接受一个函数作为参数并返回一个函数作为输出。输出根据装饰器逻辑进行修改。在本节中,我们将创建 logger 装饰器。由于我们不知道函数输入,我们将传递 *arg 和 **kwarg。*arg 和 **kwarg 参数允许我们传递任意数量的位置参数和关键字参数。让我们看看 logger 装饰器的以下实现。

示例 -

我们可以将上述装饰器应用于任何函数并修改其结果。

decorator_function = logger(function_argument)

我们也可以使用 @ 符号应用装饰器。让我们看下面的例子。

输出

----- get_text: Function start -----
The Decorator is called
----- get_text: Function end -----

2. @lru_cache

它随 Python functools 模块提供,我们可以导入它。它使用最近最少使用 (LRU) 算法返回函数的值。如果缓存已满,它会丢弃最少使用的值。此装饰器在长时间运行的任务中非常有用,这些任务在相同输入下不会更改输出,例如请求静态页面、删除网页或查询数据库。以下是 @lru_decorator 的示例。

示例 -

解释 -

要从头开始创建缓存装饰器,我们将首先向包装函数添加一个字典作为属性。此字典将用作存储输入函数返回的先前计算值的位置。

当调用输入函数时,装饰器将首先检查传递给函数的参数是否已计算并存储在缓存中;如果已存储,装饰器将返回缓存的结果。否则,装饰器将计算结果并将其添加到缓存中以备将来使用。

3. @repeat

repeat 装饰器负责多次重复。它有助于调试、压力测试或自动化多个任务的重复。让我们理解以下示例 -

示例 -

在上面的代码中,我们定义了一个名为 repeat 的装饰器,它接受多个参数。我们导入了 wraps 装饰器,它包裹在被装饰的函数周围。包装函数以等于指定次数的次数调用被装饰的函数。

输出

Hi, There
Hi, There
Hi, There
Hi, There
Hi, There
Hi, There
Hi, There
Hi, There
Hi, There
Hi, There

4. @timeit

此装饰器计算函数的执行时间并打印结果。它可用于调试或监控。让我们理解以下示例。

示例 -

输出

process_data took 1.003516 seconds to complete

5. @retry

Python 中的 @retry 装饰器是一个有用的工具,用于在函数由于某些异常而失败时自动重试该函数。此装饰器可以应用于任何函数,并且可以自定义以根据导致失败的异常类型使用不同的参数重试该函数。

以下是如何在 Python 中使用 @retry 装饰器的示例。

示例 -

解释 -

在此示例中,my_function() 函数将重试最多 3 次,每次重试之间有 2 秒的延迟。backoff 参数指定每次重试后延迟将加倍,jitter 参数指定每次重试之间的延迟将在 1 到 3 秒之间随机变化,以避免重试风暴。

默认情况下,如果引发任何异常,@retry 装饰器将重试该函数。但是,您可以通过传递 retry_on_exception 参数来指定要重试的异常列表。

在上面的代码中,my_function 将仅在引发 ValueError 或 TypeError 时重试。

请注意,@retry 装饰器不是 Python 内置的,需要单独安装。有几个库提供了 @retry 装饰器,例如 retrying 和 tenacity。

7. @wraps

此装饰器用于保留原始函数的名称文档字符串。让我们理解以下示例。

示例 -

解释 -

在上面的代码中,我们使用 @wraps 装饰器来保留原始 add_numbers() 函数的名称和文档字符串。如果没有 @wraps,被装饰的函数将具有不同的名称和文档字符串,这可能会令人困惑并使调试更加困难。

以下是如何使用 add_numbers 函数的示例。

示例 -

输出

3
add_number
This function adds two numbers

7. @rate_limited

@rate_limited 装饰器用于限制函数的调用速率,如果函数调用过于频繁,则会休眠一段时间。

示例 -

解释 -

在上面的代码中,我们使用 rate_limited 装饰器来限制函数的调用速率。max_per_second 参数指定函数每秒可以调用的最大次数。

decorate 函数在 rate_limited 装饰器内部定义,用于将原始函数包装到限速逻辑中。我们使用一个列表 last_time_called 来存储函数上次调用的时间,它允许我们在 rate_limited_function 内部更新上次调用时间,即使 last_time_called 是一个可变对象。

rate_limited_function 包装原始函数并计算自上次调用函数以来经过的时间。如果经过的时间小于最小间隔,则函数休眠最小间隔与经过时间之间的差值。然后更新上次调用时间,并使用提供的参数调用原始函数。

输出

Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world

在上面的代码中,my_function() 函数被限速为每秒最多调用 2 次。当我们在循环中调用该函数 5 次时,它只打印“Hello, world!”两次,因为限速装饰器在调用之间休眠了适当的时间。

8. @dataclass

@dataclass 装饰器用于装饰类。当我们使用 @dataclass 装饰器装饰一个类时,装饰器会根据类属性自动为该类生成几个特殊方法,例如 __init__()、__repr__()、__eq__() 等。这省去了程序员手动定义这些方法的精力,这可能很繁琐且容易出错,尤其是对于具有许多属性的类。它还提供了现成的精妙方法来很好地表示对象,将它们转换为 JSON 格式,使它们不可变等。

此装饰器在 Python 3.7 中引入,并可在标准库中使用。

让我们理解下面的例子。

示例 -

输出

Car(make='Honda', model='Civic', year=2022, price=25000.0)
Car(make='Toyota', model='Corolla', year=2022, price=24000.0)
False

解释 -

在上面的代码中,我们定义了一个 Car 类,它有四个属性:make、model、year 和 price。@dataclass 装饰器自动为该类生成几个特殊方法,包括一个以四个属性作为参数的 __init__() 方法,一个返回对象字符串表示的 __repr__() 方法,以及一个根据其属性比较两个对象的 __eq__() 方法。

然后我们创建 Car 类的两个实例 car1 和 car2,并将它们打印到控制台。最后,我们使用 == 运算符比较 car1 和 car2,它调用 @dataclass 装饰器生成的 __eq__() 方法。

9. @singledispatch

@singledispatch 装饰器是 Python functools 模块中的一个函数装饰器,它定义了一个泛型函数,该函数根据其参数类型表现不同。它提供了一种在 Python 中实现多态行为的简单方法。

下面是 @singledispatch 装饰器如何使用的示例。

示例 - from functools import singledispatch

输出

This is an integer: 42
This is a string: hello
Unknown type argument

解释 -

在上面的代码中,我们使用 @singledispatch 装饰器定义了一个名为 myfunc 的泛型函数。该函数最初定义为在调用任何参数类型时打印泛型消息。

然后我们使用 register() 方法为 int 和 str 类型定义 myfunc 的特定实现。_ 标识符用于指示函数不使用该参数。

10. @register

@register 装饰器在 Python 脚本意外终止但我们仍希望在退出前执行某些操作的情况下非常有用。使用 @register 装饰器注册函数可确保即使发生错误,该函数也会在脚本退出之前调用。

例如,我们可以使用 @register 装饰器注册一个清理函数,该函数在脚本终止前保存我们的工作或打印消息。它在长时间运行的脚本或执行重要任务的脚本中特别有用。

@register 装饰器提供了一种简单灵活的方式,无论终止原因如何,都可以在脚本退出前执行操作。

结论

装饰器是强大的工具,可以通过添加缓存、日志记录、限速和自动重试等新功能来增强代码。它们还可以用于将类转换为更通用的数据容器。

通过利用装饰器的强大功能,您可以使代码更模块化、更高效、更灵活。无论您是从事大型项目还是小型个人脚本,装饰器都可以帮助您将代码提升到一个新的水平。