python装饰器详解

什么是装饰器(Decorator)

定义:装饰器是一个接受函数或类作为参数,并返回一个新的函数或类的“函数”。

简单说:
它是一个“语法糖”,用来在不修改原函数代码的前提下,增强函数或类的功能。

举个简单例子

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"开始执行 {func.__name__}")
        result = func(*args, **kwargs)
        print(f"结束执行 {func.__name__}")
        return result
    return wrapper

@log_decorator
def say_hello(name):
    print(f"你好,{name}!")

say_hello("小明")

运行结果:

开始执行 say_hello
你好,小明!
结束执行 say_hello

解释
@log_decorator 等价于 say_hello = log_decorator(say_hello)

装饰器在函数定义阶段就被执行(不是运行时)
通过包装(wrapper)函数,我们“插入”了日志逻辑,而无需修改 say_hello() 源码。

装饰器能做什么(常见用途)

用途 示例
✅ 日志打印 记录函数的调用、输入、输出
🧠 缓存结果 functools.lru_cache 装饰计算密集函数
🔒 权限验证 检查用户是否登录或有权限访问某接口
⏱️ 性能统计 计算函数执行耗时
💬 异常捕获 统一捕获异常并格式化输出
🌐 Web框架中 Flask、FastAPI 中用来定义路由(例如 @app.route()

实现原理(底层机制)

核心概念:函数是一等公民(First-class Object)

在 Python 中:

  • 函数可以被当作参数传递;
  • 可以被返回;
  • 可以被赋值给变量;
  • 可以嵌套定义。

因此可以写出:


def decorator(func):
    def wrapper(*args, **kwargs):
        # 执行前
        print("Before call")
        result = func(*args, **kwargs)
        # 执行后
        print("After call")
        return result
    return wrapper

Python 解释器在看到:

@decorator
def foo(): pass

时,会自动执行:

foo = decorator(foo)

这就是装饰器的实现原理。
它本质上是一个“语法糖 + 高阶函数”机制。

进阶

在上面的装饰器示例中,我们都是通过新返回了一个函数wrapper而不是直接使用原函数func,为什么不能直接返回func函数呢?让我们来测试一下:

def log_decorator(func):
    print(f"开始执行 {func.__name__}")
    func()
    print(f"结束执行 {func.__name__}")
    return func

@log_decorator
def say_hello():
    print('hello world')

if __name__ == '__main__':

    print("test return raw func")
    say_hello()
    say_hello()

输出结果如下:

开始执行 say_hello
hello world
结束执行 say_hello

test return raw func
hello world
hello world

我们发现在 say_hello在执行前输出了一次日志,但是在后续的两次实际执行时都没有输出日志,这是因为什么呢?

让我们从执行时序上解释这个现象。

1、模块加载/函数定义阶段
当装饰器中直接返回原func时

模块加载/解释到 @log_decorator 这一行时,会立刻执行:say_hello = log_decorator(say_hello)
于是,

print(f"开始执行 {func.__name__}")
func() # 这里的 func 就是原始 say_hello
print(f"结束执行 {func.__name__}")
return func

所以在程序刚启动、函数还没被你手动调用前,就已经输出了:

开始执行 say_hello
hello world
结束执行 say_hello

然后之后你再调用:

say_hello()
say_hello()

注意:后续调用就只有 say_hello 本体,没有 “开始执行/结束执行” 了,因为真正带“开始执行/结束执行”的那次是在定义阶段执行掉的。

2、函数调用阶段
装饰器内返回新的函数wrapper时:

def decorator(func):
    def wrapper(*args, **kwargs):
        print("before")
        res = func(*args, **kwargs)
        print("after")
        return res
    return wrapper

@decorator
def foo():
    print("foo body")

等价于

foo = decorator(foo)   # 定义阶段:只是返回 wrapper,并没调用 foo 本体
foo()                  # 实际执行阶段:执行 wrapper -> before -> 原 foo -> after

定义阶段不会执行 foo(),只是产生一个“带壳子的 foo(wrapper)”

按照上面的分析我们知道要在执行执行阶段进行增强,就必须在装饰器内返回一个新的函数wrapper,那么这么做是否是就足够了呢?

回归到正确写法然后打印say_hello的信息

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"开始执行 {func.__name__}")
        result = func(*args, **kwargs)
        print(f"结束执行 {func.__name__}")
        return result
    return wrapper

@log_decorator
def say_hello():
    """
    say hello
    """
    print('hello world')

if __name__ == '__main__':
    print(say_hello.__name__)
    print(say_hello.__doc__)
    print(say_hello.__annotations__)

输出如下:

"wrapper"
say hello
{}

也就是说,装饰器破坏了函数的元信息(metadata),这会影响:

  • 调试体验;
  • 文档生成;
  • IDE 智能提示;
  • 类型检查工具(mypy、pylance);
  • Web 框架的函数签名自动推断(如 FastAPI)。

所以我们需要使用装饰器@wrapswrapper进行包装

from functools import wraps

@wraps(func)
def wrapper(*args, **kwargs):
    ...

等价于执行:

wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
wrapper.__annotations__ = func.__annotations__

也就是让这个“替身”看起来仍然是原来的函数。
完整的写法是:

def log_decorator(func):
    ##注意这里
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"开始执行 {func.__name__}")
        result = func(*args, **kwargs)
        print(f"结束执行 {func.__name__}")
        return result
    return wrapper

再次打印say_hello信息输出:

say_hello 
    say hello
     {}

一句话总结

问题 原因
为什么要多一层 wrapper() 因为装饰器要修改函数“调用时”的行为,必须返回一个新的函数去包裹原函数
为什么要加 @wraps(func) 因为经过装饰后,函数名、注释、签名都会丢失,wraps 用来保留这些信息

与Java Aop的区别

从用法上来看,python中装饰器的用法与java中Aop的用法(使用注解时)十分相似,以下是两者的异同:

对比项 Python 装饰器 Java AOP(如 Spring AOP)
实现机制 基于函数或类的高阶函数 基于动态代理或字节码增强(如 JDK Proxy、CGLIB)
作用范围 作用于函数/类定义级别 作用于方法级别(类的成员方法)
粒度控制 显式声明(在代码上用 @decorator 通常通过注解或切入点表达式(Pointcut)动态匹配
运行时控制 无法动态织入,必须在定义时装饰 支持运行时织入(可动态开启/关闭)
使用复杂度 简单直接,可读性强 强大但复杂,依赖框架和代理机制
性能开销 较小(纯函数封装) 相对更高(代理 + 反射)
posted @ 2026-01-08 19:45  DeepSky丶  阅读(6)  评论(0)    收藏  举报