人人都会AI编程

装饰器本质与实现原理

更新时间:2026-07-12

装饰器是 Python 中非常实用且体现“一切皆对象”思想的特性。简单说,装饰器就是一个接受函数作为参数并返回新函数的可调用对象,它允许在不修改原函数代码的前提下,给函数增加额外的功能。

本质:一个函数包装另一个函数

装饰器的核心操作是“替换”。当你用 @decorator 修饰一个函数时,Python 实际上做了这件事:

# 使用装饰器语法
@decorator
def func():
    pass

# 等价于手动调用
func = decorator(func)

也就是说,func 这个名字不再指向原来的函数,而是指向了 decorator(func) 的返回值。这个返回值通常是一个新函数(或任何可调用对象),它内部会调用原函数,并在其前后添加额外的逻辑。

实现原理:闭包 + 函数对象

绝大多数装饰器都是用闭包实现的。我们来看一个最基础的计时装饰器:

import time

def timer(func):                 # 1. 接受被装饰的函数
    def wrapper(*args, **kwargs): # 2. 定义包装函数,使用闭包捕获 func
        start = time.time()
        result = func(*args, **kwargs)  # 3. 调用原函数
        end = time.time()
        print(f"{func.__name__} 运行时间: {end - start:.4f} 秒")
        return result
    return wrapper               # 4. 返回包装后的函数

@timer
def slow_function():
    time.sleep(1)
    print("完成")

slow_function()
# 输出:
# 完成
# slow_function 运行时间: 1.0002 秒

关键点解析

  • timer 是一个高阶函数,它接受函数 func 作为参数。
  • wrapper 是一个闭包,它“记住”了外层函数传进来的 func,即使 timer 调用结束后,wrapper 依然能够访问 func。这就是闭包保存变量的机制。
  • args, *kwargs 确保包装后的函数可以接受任意参数,不影响原函数的调用方式。
  • 最后 timer 返回 wrapper,于是 slow_function 这个名称就指向了 wrapper 函数。

保持原函数的“身份”:functools.wraps

上面的装饰器存在一个隐藏问题:slow_function.name 会变成 "wrapper",它的文档字符串等元信息也会丢失。因为最终指向的是 wrapper,而不是原函数。这可能导致调试时产生困惑。

解决办法是使用 functools.wraps,它会把原函数的元信息复制到包装函数中:

from functools import wraps

def timer(func):
    @wraps(func)           # 保留 func 的 __name__, __doc__ 等
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 运行时间: {end - start:.4f} 秒")
        return result
    return wrapper

现在 slow_function.name 依然为 "slow_function"。在生产环境编写装饰器时,加上 @wraps 是一个标准实践。

装饰器为什么能工作:函数是一等公民

装饰器能够实现,根本原因在于 Python 中函数是一等对象。这意味着:

  • 函数可以被赋值给变量
  • 函数可以作为参数传递给另一个函数
  • 函数可以在另一个函数内部定义并返回

这些特性组合起来,就构成了装饰器的全部要素。

一个更实际的例子:权限检查

假设我们在写一个简单的 API,有些操作需要管理员权限:

def require_admin(func):
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.get("is_admin"):
            raise PermissionError("需要管理员权限")
        return func(user, *args, **kwargs)
    return wrapper

@require_admin
def delete_all_data(user):
    print("所有数据已删除")

admin_user = {"name": "root", "is_admin": True}
normal_user = {"name": "guest", "is_admin": False}

delete_all_data(admin_user)  # 正常执行
delete_all_data(normal_user) # 抛出 PermissionError

这里 require_admin 是一个典型的装饰器:它在调用 func 之前进行了一个权限判断,如果不符合条件就直接抛出异常,否则才执行真正的业务逻辑。这个检查逻辑与 delete_all_data 函数本身完全解耦,增删检查规则时都不需要改动业务函数。

总结

  • 本质:装饰器是一个把一个函数替换成另一个函数的语法糖。
  • 原理:利用函数一等对象的特性,通过闭包捕获原函数,返回一个新的包装函数。
  • 核心要点:接受函数、返回函数、用 @wraps 保留元信息、args, *kwargs 保持通用性。
  • 实用之处:在日志记录、性能监控、权限验证、缓存、事务控制等横切关注点上,装饰器提供了极其优雅的复用方案。