文章目录

Python 高级特性:装饰器的原理与自定义实现

发布于 2026-04-13 22:27:23 · 浏览 45 次 · 评论 0 条

装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能。装饰器的返回值也是一个函数对象。利用 Python 函数作为一等对象的特性,我们可以将函数作为参数传递给另一个函数,并在该函数内部对其进行增强处理。


第一阶段:理解函数作为对象的基础

在编写装饰器之前,必须先理解 Python 中函数也是对象,可以被赋值给变量,也可以作为参数传递。

  1. 打开 Python 编辑器或交互式终端。
  2. 定义一个简单的 say_hello 函数。
def say_hello():
    print("Hello, Python!")
  1. 创建一个变量 greet,并将 say_hello 函数赋值给它。注意这里没有使用括号 (),因此是将函数本身赋值,而不是调用它。
greet = say_hello
  1. 调用 greet 变量,观察是否执行了原函数。
greet()

输出结果将是 Hello, Python!。这证明了函数名可以像变量一样被传递和引用。


第二阶段:构建闭包结构

闭包是装饰器的核心。闭包是指在一个外部函数中定义了一个内部函数,内部函数引用了外部函数的变量,然后外部函数返回这个内部函数。

  1. 定义一个外部函数 outer_wrapper,它接收一个函数作为参数(我们暂且称之为 original_func)。
  2. 在外部函数内部定义一个嵌套的内部函数 inner_wrapper
  3. 在内部函数中编写增强逻辑(例如打印日志),然后 调用 传入的 original_func
  4. 让外部函数返回这个内部函数 inner_wrapper
def outer_wrapper(original_func):
    # 内部函数,用于包装原始函数
    def inner_wrapper():
        print("--- 开始执行 ---")
        original_func()
        print("--- 执行结束 ---")

    # 返回内部函数对象(注意不要加括号)
    return inner_wrapper

第三阶段:手动应用装饰器逻辑

现在我们已经有了一个包装器,让我们不使用 @ 语法糖,手动来装饰一个函数。

  1. 定义一个目标函数 show_message
def show_message():
    print("我是核心业务逻辑。")
  1. 执行装饰过程。调用 outer_wrapper 并传入 show_message,将返回结果(即 inner_wrapper)重新赋值给 show_message
show_message = outer_wrapper(show_message)
  1. 调用 show_message
show_message()

此时,show_message 实际上指向的是 inner_wrapper 函数,因此输出会包含前后两条日志线,中间是核心业务逻辑。


第四阶段:使用 @ 语法糖

Python 提供了 @ 符号来简化“将函数作为参数传递并重新赋值”的过程。

  1. 保留之前定义的 outer_wrapper 函数。
  2. 定义一个新的函数 send_notification,并在其定义行上方 添加 @outer_wrapper
@outer_wrapper
def send_notification():
    print("发送通知邮件。")
  1. 直接调用 send_notification。你会发现它自动具备了“开始执行”和“执行结束”的日志功能,而无需手动执行 send_notification = outer_wrapper(send_notification)

第五阶段:处理参数与返回值

目前的装饰器有一个致命缺陷:它只能装饰没有任何参数的函数,并且无论原始函数返回什么,它都会返回 None(因为 inner_wrapper 没有显式返回值)。我们需要完善它。

  1. 修改 inner_wrapper 的定义,使其接收任意数量的位置参数 *args 和关键字参数 **kwargs
  2. 在调用 original_func 时,将这些参数传递进去。
  3. 捕获 original_func 的返回值,并 inner_wrapper 末尾返回该值。
def smart_wrapper(original_func):
    def inner_wrapper(*args, **kwargs):
        print(f">> 日志: 函数 {original_func.__name__} 即将执行,参数: {args}, {kwargs}")

        # 接收原始函数的返回值
        result = original_func(*args, **kwargs)

        print(f">> 日志: 函数 {original_func.__name__} 执行完毕,返回值: {result}")
        return result

    return inner_wrapper
  1. 定义一个带参数和返回值的函数进行测试。
@smart_wrapper
def add(a, b):
    return a + b
  1. 调用 add(3, 5)。装饰器将正确打印日志并返回计算结果 8

第六阶段:保留原函数的元数据

当我们使用装饰器后,被装饰函数的元数据(如函数名 __name__、文档字符串 __doc__)会被替换为包装器的元数据。这可能会导致一些依赖反射的代码出错。

  1. 查看被装饰函数的名字。
print(add.__name__)

你会发现输出是 inner_wrapper,而不是 add。这显然不是我们想要的。

  1. 引入 Python 标准库 functools 中的 wraps 装饰器。
  2. inner_wrapper 定义上方 添加 @functools.wraps(original_func)
import functools

def smart_wrapper(original_func):
    @functools.wraps(original_func)
    def inner_wrapper(*args, **kwargs):
        print(f">> 日志: {original_func.__name__} 执行中...")
        return original_func(*args, **kwargs)
    return inner_wrapper
  1. 重新定义 add 函数(使用新的 smart_wrapper)。
  2. 再次打印 add.__name__。现在它会正确显示 add

第七阶段:实现带参数的装饰器

有时候我们需要装饰器本身接收参数,例如 @repeat(times=3),让函数重复执行 3 次。这需要三层嵌套结构。

  1. 定义最外层函数 repeat,它接收装饰器的参数(如 times)。
  2. repeat 内部 定义中间层函数 decorator,它接收真正的函数 func
  3. decorator 内部 定义最内层函数 wrapper,它负责实际的业务逻辑和函数调用。
import functools

def repeat(times):
    # 第一层:接收装饰器参数
    def decorator(func):
        # 第二层:接收被装饰的函数
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 第三层:执行逻辑
            results = []
            for _ in range(times):
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator
  1. 使用带参数的装饰器装饰一个函数。
@repeat(times=3)
def greet(name):
    print(f"Hello, {name}!")
  1. 调用 greet("Alice")。该函数将被执行 3 次。

装饰器执行流程图解

为了更直观地理解带参数装饰器的执行顺序,参考下方的逻辑流向。

graph TD A["Python 解释器加载代码"] --> B["遇到 @repeat(times=3)"] B --> C["调用 repeat(3)"] C --> D["返回 decorator 函数"] D --> E["调用 decorator(func)"] E --> F["返回 wrapper 函数"] F --> G["将原函数名指向 wrapper"] G --> H["代码运行时调用 greet_name"] H --> I["进入 wrapper 逻辑"] I --> J["循环执行 func_name 3 次"]

装饰器的核心在于“闭包”和“函数作为参数传递”。通过层层包裹,我们在不修改原有业务逻辑代码的前提下,实现了日志记录、性能测试、权限校验、重复执行等横切关注点的功能。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文