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)。
所以我们需要使用装饰器@wraps对wrapper进行包装
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)动态匹配 |
| 运行时控制 | 无法动态织入,必须在定义时装饰 | 支持运行时织入(可动态开启/关闭) |
| 使用复杂度 | 简单直接,可读性强 | 强大但复杂,依赖框架和代理机制 |
| 性能开销 | 较小(纯函数封装) | 相对更高(代理 + 反射) |
浙公网安备 33010602011771号