如何使用装饰器语法修改 Python 函数功能

PythonPythonBeginner
立即练习

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

简介

Python 装饰器是一个强大的工具,它允许你在不改变函数核心逻辑的情况下修改函数的行为。在本教程中,我们将探讨装饰器语法,并学习如何使用它来增强 Python 函数的功能。从基本的装饰技术到高级方法,你将全面了解 Python 编程语言中这个多功能的特性。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/FunctionsGroup -.-> python/lambda_functions("Lambda Functions") python/FunctionsGroup -.-> python/scope("Scope") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") subgraph Lab Skills python/arguments_return -.-> lab-398094{{"如何使用装饰器语法修改 Python 函数功能"}} python/lambda_functions -.-> lab-398094{{"如何使用装饰器语法修改 Python 函数功能"}} python/scope -.-> lab-398094{{"如何使用装饰器语法修改 Python 函数功能"}} python/decorators -.-> lab-398094{{"如何使用装饰器语法修改 Python 函数功能"}} end

介绍 Python 装饰器

Python 装饰器是一项强大的语言特性,它使你能够在不改变函数源代码的情况下修改其行为。它们提供了一种用额外功能包装函数的方式,以简洁且可复用的方式增强或改变其行为。

什么是 Python 装饰器?

Python 中的装饰器是一种通过用另一个函数包装来 “装饰” 函数的方式。包装函数可以在调用原始函数之前或之后添加额外功能,甚至修改原始函数的参数和返回值。

装饰器使用 @ 符号定义,后面跟着装饰器函数的名称,放在函数定义之前。这种语法是将装饰器应用于函数的一种简写方式。

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

def log_arguments(func):
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__},参数为 args={args},kwargs={kwargs}")
        return func(*args, **kwargs)
    return wrapper

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

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

输出:

调用 add_numbers,参数为 args=(2, 3),kwargs={}
5

在这个示例中,log_arguments 装饰器包装了 add_numbers 函数,在调用原始函数之前添加了记录参数的功能。

使用装饰器的好处

装饰器有几个好处:

  1. 代码复用:装饰器使你能够轻松地将相同的功能应用于多个函数,促进代码复用和可维护性。
  2. 关注点分离:装饰器有助于将函数的核心功能与附加功能分离,使代码更具条理性和模块化。
  3. 可读性:装饰器语法使你很容易理解哪些函数被装饰了以及添加了什么功能。
  4. 灵活性:装饰器可以堆叠,允许你将多层功能应用于单个函数。

装饰器的常见用例

装饰器在 Python 中有广泛的用途,包括:

  • 日志记录和调试:记录函数的输入和输出,如前面的示例所示。
  • 缓存:缓存函数的结果以提高性能。
  • 认证和授权:检查用户是否有权访问特定函数。
  • 计时和性能测量:测量函数的执行时间。
  • 重试失败的调用:如果函数失败,自动重试,可选择退避和重试策略。

在下一节中,我们将更详细地探讨如何创建和使用装饰器。

装饰函数

定义一个简单的装饰器

装饰器函数的基本结构如下:

def decorator_function(func):
    def wrapper(*args, **kwargs):
        ## 在调用原始函数之前执行某些操作
        result = func(*args, **kwargs)
        ## 在调用原始函数之后执行某些操作
        return result
    return wrapper

装饰器函数接受一个函数作为参数,并返回一个包装原始函数的新函数。包装函数可以在调用原始函数之前和/或之后执行额外的任务。

下面是一个简单的装饰器示例,它记录函数的执行时间:

import time

def measure_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"函数 {func.__name__} 执行耗时 {end_time - start_time:.2f} 秒。")
        return result
    return wrapper

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

fibonacci(30)

输出:

函数 fibonacci 执行耗时 0.31 秒。

向装饰器传递参数

装饰器也可以接受参数,这提供了更大的灵活性和定制性。为此,你需要创建一个装饰器工厂,它是一个返回装饰器函数的函数。

下面是一个装饰器示例,它允许你指定函数应该重试的次数:

def retry(max_retries):
    def decorator(func):
        def wrapper(*args, **kwargs):
            retries = 0
            while retries < max_retries:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"函数 {func.__name__} 执行失败。正在重试... (第 {retries + 1}/{max_retries} 次尝试)")
                    retries += 1
            raise Exception(f"函数 {func.__name__} 在 {max_retries} 次重试后仍失败。")
        return wrapper
    return decorator

@retry(max_retries=3)
def divide(a, b):
    return a / b

try:
    result = divide(10, 0)
except Exception as e:
    print(e)

输出:

函数 divide 执行失败。正在重试... (第 1/3 次尝试)
函数 divide 执行失败。正在重试... (第 2/3 次尝试)
函数 divide 执行失败。正在重试... (第 3/3 次尝试)
函数 divide 在 3 次重试后仍失败。

在这个示例中,retry 装饰器工厂接受一个 max_retries 参数,该参数用于确定在引发异常之前装饰函数应该重试的次数。

堆叠装饰器

装饰器可以堆叠,允许你将多层功能应用于单个函数。装饰器的应用顺序很重要,因为它决定了包装函数的执行顺序。

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

在这个示例中,greet 函数首先用 uppercase 装饰器进行装饰,然后再用 reverse 装饰器进行装饰。最终结果是一个函数,它返回大写且反转后的问候语。

装饰器的顺序很重要,因为它决定了包装函数的执行顺序。在这种情况下,uppercase 装饰器首先应用,然后 reverse 装饰器应用于 uppercase 装饰器的结果。

高级装饰器技术

基于类的装饰器

除了基于函数的装饰器外,Python 还支持基于类的装饰器。当你需要在装饰器中维护状态或配置信息时,这种方法会很有用。

下面是一个基于类的装饰器示例,它缓存函数的结果:

class cache:
    def __init__(self, func):
        self.func = func
        self.cache = {}

    def __call__(self, *args, **kwargs):
        key = str(args) + str(kwargs)
        if key in self.cache:
            return self.cache[key]
        else:
            result = self.func(*args, **kwargs)
            self.cache[key] = result
            return result

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

print(fibonacci(30))
print(fibonacci(30))

输出:

832040
832040

在这个示例中,cache 类用作装饰器。当装饰器应用于函数时,会调用 __init__ 方法,每次调用被装饰的函数时,会调用 __call__ 方法。cache 字典用于存储之前函数调用的结果,这样后续使用相同参数的调用可以从缓存中获取结果。

装饰器工厂

装饰器工厂是一种创建可以用参数定制的装饰器的方法。这使你能够创建更灵活、可复用的装饰器。

下面是一个装饰器工厂示例,它允许你指定函数可以接受的最大参数数量:

def max_arguments(max_args):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if len(args) + len(kwargs) > max_args:
                raise ValueError(f"函数 {func.__name__} 最多只能接受 {max_args} 个参数。")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@max_arguments(max_args=3)
def greet(name, greeting, exclamation="!"):
    return f"{greeting}, {name}{exclamation}"

print(greet("LabEx", "Hello"))
print(greet("LabEx", "Hello", "?"))
print(greet("LabEx", "Hello", "?", "extra"))

输出:

Hello, LabEx!
Hello, LabEx?
ValueError: 函数 greet 最多只能接受 3 个参数。

在这个示例中,max_arguments 装饰器工厂接受一个 max_args 参数,该参数用于确定被装饰函数可以接受的最大参数数量。然后,decorator 函数用参数验证逻辑包装原始函数。

保留元数据

当你将装饰器应用于函数时,原始函数的元数据(如名称、文档字符串和签名)会丢失。如果你在诸如 Web 框架或命令行界面等元数据很重要的上下文中使用该函数,这可能会有问题。

为了保留原始函数的元数据,你可以使用 functools.wraps 装饰器,它将相关元数据从原始函数复制到包装函数。

from functools import wraps

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

@uppercase
def greet(name):
    """返回给定名称的问候语。"""
    return f"Hello, {name}!"

print(greet.__name__)
print(greet.__doc__)

输出:

greet
返回给定名称的问候语。

在这个示例中,wraps 装饰器用于将 greet 函数的 __name____doc__ 属性复制到 wrapper 函数。这确保了被装饰的函数保留其原始元数据,这对于各种用例可能很重要。

总结

Python 装饰器是一种强大且灵活的语言特性,它允许你以简洁且可复用的方式修改函数的行为。通过理解基本概念、常见用例和高级技术,你可以利用装饰器编写更模块化、可维护且富有表现力的 Python 代码。

请记住,有效使用装饰器的关键是在添加功能与保留原始函数的意图和行为之间取得平衡。通过实践和创造力,你可以在 Python 项目中充分发挥装饰器的潜力。

总结

在本教程结束时,你将扎实掌握 Python 装饰器以及如何使用它们来扩展函数的功能。你将学习基本语法,探索各种装饰器技术,并了解如何创建自己的自定义装饰器以满足特定需求。掌握 Python 装饰器将使你能够编写更高效、模块化和可维护的代码,使你成为更熟练的 Python 程序员。