Python装饰器
Published in:2025-12-11 |
Words: 1.4k | Reading time: 5min | reading:

【Python进阶】一文彻底搞懂“装饰器”:从原理到实战(附 Python vs Java 深度对比)

一、 什么是装饰器?(通俗理解)

想象一下,你有一部手机(原函数),它能打电话(核心功能)。
现在你想给手机增加“防摔”和“美观”的功能,你不会把手机拆了重新焊一个外壳,而是直接给它套一个手机壳

装饰器就是这个“手机壳”。

  • 定义:装饰器本质上是一个Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能。
  • 核心:它接收一个函数作为参数,并返回一个新的函数。

二、 为什么要用装饰器?(作用)

假设你开发了一个电商系统,里面有 100 个函数处理不同的业务。现在老板提了个需求:“给这 100 个函数都加上日志记录,我要看它们执行了多久。”

  • 笨办法:打开这 100 个函数,挨个改代码。-> 维护噩梦
  • 装饰器:写一个通用的 @timer 装饰器,给函数戴上即可。
    • 优点代码复用(DRY原则),逻辑分离(业务代码纯净,非业务代码抽离)。

三、 装饰器的原理(抽丝剥茧)

要理解装饰器,必须理解 Python 的两个特性:

  1. 函数是对象:函数可以赋值给变量,也可以作为参数传递。
  2. 闭包:函数内部可以定义函数,内部函数可以访问外部函数的变量。

1. 最原始的样子

1
2
3
4
5
6
7
8
9
10
11
12
13
def my_decorator(func):
def wrapper():
print(">>> 装饰前")
func()
print("<<< 装饰后")
return wrapper

def fight():
print("士兵正在战斗...")

# 核心逻辑:用 wrapper 替换掉原来的 fight
fight = my_decorator(fight)
fight()

2. 使用 @ 语法糖

1
2
3
@my_decorator  # 等同于 fight = my_decorator(fight)
def fight():
print("士兵正在战斗...")

四、 万能装饰器模板(直接抄作业)

实际开发中,被装饰的函数可能有参数,可能有返回值。请背诵以下模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import time
import functools

def timer(func):
# @functools.wraps 作用:保留原函数的元信息(如函数名、文档说明)
@functools.wraps(func)
def wrapper(*args, **kwargs): # 1. 接收任意参数
print(f"--- 开始执行 {func.__name__} ---")

# 2. 执行原函数,并接收返回值
result = func(*args, **kwargs)

print(f"--- 执行结束 ---")

# 3. 必须返回原函数的结果
return result

return wrapper

五、 实战场景应用

  1. 权限校验@login_required (Flask/Django 中最常见)。
  2. 性能分析@timer 计算函数耗时。
  3. 缓存@lru_cache (Python 内置) 缓存计算结果,避免重复计算。
  4. 自动重试@retry 网络请求失败自动重连。

六、 特别篇:Python 装饰器 vs Java 注解 (核心考点)

这是很多面试官喜欢问的高阶问题,也是很多 Java 转 Python 选手的认知误区。

一句话总结:Python 装饰器是“干活的”,Java 注解是“贴标签的”。

1. 类别与本质的区别

维度 Python 装饰器 (Decorator) Java 注解 (Annotation)
本质 是一个函数(或类) 是一种元数据(Metadata)
状态 主动的。它是可执行的代码。 被动的。它只是一个标签,自己不会动。
生效时机 加载时运行。Python 文件一加载,装饰器代码就执行了,原函数直接被替换。 运行时/编译时读取。需要额外的代码(反射或编译器)来读取标签并执行逻辑。
实现机制 闭包 + 高阶函数 反射 (Reflection) + AOP (动态代理)

2. 原理对比图解

Python 的逻辑(狸猫换太子)

Python 的装饰器直接修改了函数对象。

1
2
3
4
5
6
@log
def hello(): pass

# 等价于:
hello = log(hello)
# hello 变成了一个新的函数 wrapper,原来的 hello 被包在里面了。

Java 的逻辑(说明书与阅读者)

Java 的注解就像是给杯子贴了个“易碎品”的标签。杯子还是那个杯子,不会变。
但是,Spring 框架(容器)看到了这个标签,就会在调用这个杯子的时候,生成一个**代理对象(Proxy)**来小心翼翼地处理它。

1
2
3
4
5
6
7
@Transactional  // 这是一个标签
public void saveUser() { ... }

// Java 虚拟机运行原理:
// 1. Spring 扫描到 @Transactional 标签。
// 2. Spring 利用“动态代理”生成一个新类。
// 3. 在新类里写代码:开启事务 -> 调用 saveUser() -> 提交事务。

3. 使用上的体感区别

  • 编写难度

    • Python:极其简单。写个函数就能当装饰器用。
    • Java:比较繁琐。你需要先定义注解接口 (@interface),然后写切面类 (@Aspect),定义切点 (@Pointcut) 和通知 (@Around),通常需要依赖 Spring 框架才能用得顺手。
  • 灵活性

    • Python:极高。装饰器里可以写任意逻辑,甚至可以把函数变成类,把类变成函数。
    • Java:规范性强。注解主要用于配置,逻辑与业务代码完全分离(解耦更彻底)。

4. 举个栗子:实现“日志记录”

Python 写法:

1
2
3
4
5
6
7
8
9
# 定义即实现
def log(func):
def wrapper(*args, **kw):
print("Log: Start")
return func(*args, **kw)
return wrapper

@log # 直接生效
def do_work(): ...

Java 写法 (伪代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 定义标签
@interface Log {}

// 2. 只有标签没用,还得写个“切面”来识别标签并干活
@Aspect
class LogAspect {
@Around("@annotation(Log)") // 监听贴了Log标签的方法
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("Log: Start");
return joinPoint.proceed(); // 执行原方法
}
}

// 3. 使用
@Log
public void doWork() { ... }

七、 总结

  1. Python 装饰器高阶函数,利用闭包原理,在加载时直接替换/包装了原函数。它是修改结构的艺术。
  2. Java 注解元数据标签,利用反射和动态代理(AOP)原理,在运行时动态增强。它是配置与逻辑分离的艺术。
Prev:
FAISS vs Chroma 向量数据库
Next:
从 CNN 视觉感知到 Transformer 全局认知