如何在单个 Python 函数上使用多个装饰器

PythonPythonBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

Python 装饰器是一个强大的工具,它允许你在不改变函数源代码的情况下修改函数的行为。在本教程中,我们将探讨如何在单个 Python 函数上使用多个装饰器,为代码重用和函数增强开辟一个充满可能性的世界。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/FunctionsGroup -.-> python/lambda_functions("Lambda Functions") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") python/AdvancedTopicsGroup -.-> python/context_managers("Context Managers") subgraph Lab Skills python/function_definition -.-> lab-397700{{"如何在单个 Python 函数上使用多个装饰器"}} python/arguments_return -.-> lab-397700{{"如何在单个 Python 函数上使用多个装饰器"}} python/lambda_functions -.-> lab-397700{{"如何在单个 Python 函数上使用多个装饰器"}} python/decorators -.-> lab-397700{{"如何在单个 Python 函数上使用多个装饰器"}} python/context_managers -.-> lab-397700{{"如何在单个 Python 函数上使用多个装饰器"}} end

理解 Python 装饰器

Python 中的装饰器是什么?

Python 中的装饰器是一种强大且灵活的方式,用于在不改变函数或类的源代码的情况下修改其行为。它们是用另一个函数“包装”一个函数的方法,使包装函数能够在原始函数被调用之前和/或之后执行代码。

为什么要使用装饰器?

装饰器可用于各种任务,例如:

  • 记录函数调用
  • 缓存函数结果
  • 实施访问控制
  • 测量函数执行时间
  • 重试失败的函数调用

装饰器是如何工作的?

Python 中的装饰器使用 @ 符号定义,后面跟着装饰器函数。装饰器函数以一个函数作为参数,执行一些额外的处理,然后返回一个新的函数,该函数可以代替原始函数被调用。

下面是一个简单的装饰器函数示例,它记录传递给函数的参数:

def log_args(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args} and kwargs={kwargs}")
        return func(*args, **kwargs)
    return wrapper

@log_args
def add_numbers(a, b):
    return a + b

result = add_numbers(2, 3)
print(result)

这将输出:

Calling add_numbers with args=(2, 3) and kwargs={}
5

嵌套装饰器

装饰器可以嵌套,允许你对单个函数应用多个装饰器。装饰器的应用顺序很重要,因为它决定了装饰器函数的执行顺序。

def uppercase(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

def reverse(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result[::-1]
    return wrapper

@uppercase
@reverse
def greet(name):
    return f"Hello, {name}!"

print(greet("LabEx"))

这将输出:

!XEbal,OLLEH

装饰器参数

装饰器也可以接受参数,这使你能够自定义装饰器的行为。当你想要创建一个可以以不同方式配置的装饰器时,这很有用。

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            result = ""
            for _ in range(n):
                result += func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    return f"Hello, {name}!"

print(greet("LabEx"))

这将输出:

Hello, LabEx!Hello, LabEx!Hello, LabEx!

应用多个装饰器

装饰器的应用顺序

当你对一个函数应用多个装饰器时,它们的应用顺序很重要。装饰器是从下往上应用的,这意味着最内层的装饰器首先被应用,最外层的装饰器最后被应用。

def uppercase(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

def reverse(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result[::-1]
    return wrapper

@uppercase
@reverse
def greet(name):
    return f"Hello, {name}!"

print(greet("LabEx"))

这将输出:

!XEBAL,OLLEH

堆叠装饰器

你也可以通过一个接一个地应用多个装饰器来堆叠它们。这等同于嵌套装饰器,但它可以使代码更具可读性。

def uppercase(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

def reverse(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result[::-1]
    return wrapper

def greet(name):
    return f"Hello, {name}!"

greet_upper_reverse = uppercase(reverse(greet))
print(greet_upper_reverse("LabEx"))

这将输出:

!XEBAL,OLLEH

装饰方法

装饰器也可用于修改类中的方法的行为。同样的原则适用,但装饰器函数必须将 self 参数作为第一个参数。

def log_method(func):
    def wrapper(self, *args, **kwargs):
        print(f"Calling {func.__name__} on {self.__class__.__name__} with args={args} and kwargs={kwargs}")
        return func(self, *args, **kwargs)
    return wrapper

class Person:
    def __init__(self, name):
        self.name = name

    @log_method
    def greet(self, message):
        return f"{message}, {self.name}!"

person = Person("LabEx")
print(person.greet("Hello"))

这将输出:

Calling greet on Person with args=('Hello',) and kwargs={}
Hello, LabEx!

装饰类

装饰器也可用于修改整个类的行为。在这种情况下,装饰器函数以一个类作为参数,并返回一个具有所需行为的新类。

def singleton(cls):
    instances = {}

    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper

@singleton
class MyClass:
    def __init__(self, value):
        self.value = value

obj1 = MyClass(42)
obj2 = MyClass(24)

print(obj1 is obj2)  ## True
print(obj1.value)    ## 42
print(obj2.value)    ## 42

在这个例子中,singleton 装饰器确保无论 MyClass 类被实例化多少次,都只会创建一个实例。

实际应用中的装饰器示例

日志记录装饰器

装饰器的一个常见用例是记录函数调用。这对于调试、监控或审计目的可能很有用。

def log_function_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args} and kwargs={kwargs}")
        return func(*args, **kwargs)
    return wrapper

@log_function_call
def add_numbers(a, b):
    return a + b

result = add_numbers(2, 3)
print(result)

这将输出:

Calling add_numbers with args=(2, 3) and kwargs={}
5

缓存装饰器

装饰器还可用于缓存代价高昂的函数调用结果,从而提高性能。

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n <= 1:
        return n
    else:
        return (fibonacci(n-1) + fibonacci(n-2))

print(fibonacci(100))

functools 模块中的 lru_cache 装饰器提供了一种简单的方法来为函数结果实现最近最少使用(LRU)缓存。

访问控制装饰器

装饰器可用于对函数或方法实施访问控制,确保只有授权用户才能访问某些功能。

from functools import wraps

def require_admin(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if not is_admin(args[0]):
            raise ValueError("Access denied. You must be an admin.")
        return func(*args, **kwargs)
    return wrapper

class User:
    def __init__(self, name, is_admin):
        self.name = name
        self.is_admin = is_admin

    @require_admin
    def delete_user(self, user_to_delete):
        print(f"Deleting user: {user_to_delete.name}")

admin = User("LabEx", True)
regular_user = User("John", False)

admin.delete_user(regular_user)  ## 可行
regular_user.delete_user(admin)  ## 引发 ValueError

在这个示例中,require_admin 装饰器在允许操作继续之前,会检查调用 delete_user 方法的用户是否为管理员。

重试装饰器

装饰器还可用于为可能因临时问题(如网络错误或 API 速率限制)而失败的函数实现重试机制。

import time
from functools import wraps

def retry(max_retries=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            retries = 0
            while retries < max_retries:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Function {func.__name__} failed. Retrying... ({retries+1}/{max_retries})")
                    retries += 1
                    time.sleep(delay)
            raise Exception(f"Maximum number of retries ({max_retries}) reached for function {func.__name__}")
        return wrapper
    return decorator

@retry(max_retries=3, delay=2)
def flaky_function():
    ## 模拟一个有 50% 几率失败的不可靠函数
    if random.random() < 0.5:
        raise Exception("Oops, something went wrong!")
    return "Success!"

print(flaky_function())

在这个示例中,retry 装饰器会在引发异常之前,自动对 flaky_function 重试最多 3 次,每次尝试之间延迟 2 秒。

这些只是 Python 中装饰器众多实际应用用例中的几个示例。装饰器是一个强大且灵活的工具,可以帮助你编写更模块化、可维护和可重用的代码。

总结

在本教程结束时,你将对如何在单个 Python 函数上应用多个装饰器有扎实的理解。你将学习实际示例和现实世界中的用例,从而能够编写更模块化、灵活且易于维护的 Python 代码。