高频考点-Python中的装饰器的用法?

这是一个非常经典且重要的Python进阶知识点。

一句话理解:装饰器就是一个“给函数增加额外功能,但不修改函数内部代码”的工具。就像给手机套上手机壳——手机本身没变,但多了防摔功能。


1. 基础认知:一切皆函数

在讲装饰器前,先记住两点:

  • Python中,函数可以被当作参数传给另一个函数。

  • Python中,函数可以嵌套定义

2. 手动实现一个最简单的装饰器

假设你想知道一个函数运行了多长时间,但不想在每个函数里都写计时代码。

import time # 这是一个装饰器函数 def timer(func): def wrapper(*args, **kwargs): # *args, **kwargs 保证能接收任意参数 start = time.time() result = func(*args, **kwargs) # 真正执行原函数 end = time.time() print(f"函数 {func.__name__} 运行了 {end - start:.4f} 秒") return result return wrapper # 使用装饰器(语法糖 @) @timer def slow_function(): time.sleep(1) return "完成" # 调用时,完全无感知 print(slow_function()) # 输出:函数 slow_function 运行了 1.0002 秒 # 完成

背后原理:@timer等价于执行了slow_function = timer(slow_function),也就是把原函数替换成了wrapper


3. 带参数的装饰器(三层结构)

如果你的装饰器本身需要接收参数(比如指定超时时间),需要再包装一层:

def repeat(times): # 这是装饰器工厂,接收参数 def decorator(func): # 这是真正的装饰器 def wrapper(*args, **kwargs): for _ in range(times): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(times=3) # 调用时带上参数 def say_hello(): print("Hello") say_hello() # 会打印 3 次 Hello

4. 保留原函数的元信息(重要!)

使用装饰器后,原函数的__name____doc__等会被wrapper覆盖,调试时会困惑。标准做法是加上functools.wraps

from functools import wraps def timer(func): @wraps(func) # 这一行把原函数的信息复制给 wrapper def wrapper(*args, **kwargs): # ... 计时逻辑 return func(*args, **kwargs) return wrapper

5. 类装饰器(用类来实现)

除了用函数,还可以用类来实现装饰器,通过__call__魔法方法:

class CountCalls: def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 print(f"第 {self.count} 次调用") return self.func(*args, **kwargs) @CountCalls def test(): print("执行") test() # 第 1 次调用 test() # 第 2 次调用

6. 实际开发中最常用的场景

场景说明
登录校验检查用户是否登录,未登录则跳转
权限控制检查当前用户是否有权限执行该操作
日志记录自动记录函数的入参、返回值、异常
缓存(如@lru_cache对相同参数直接返回缓存结果
重试机制函数报错时自动重试 N 次
路由注册(Flask/FastAPI)@app.route('/')就是装饰器

7. 一个常见的坑:装饰器顺序

多个装饰器叠加时,执行顺序是自下而上(从靠近函数的开始):

@a @b @c def func(): pass # 实际执行顺序:func = a(b(c(func)))

8. 如果你刚接触,给你一个万能模板

以后写任何装饰器,直接套这个模板,不会出错:

from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): # 在调用前做点什么 result = func(*args, **kwargs) # 在调用后做点什么 return result return wrapper

最后送你一个思考:装饰器本质上是“面向切面编程”(AOP)的一种实现,它把业务逻辑(核心功能)和辅助逻辑(日志、计时、校验)分离开,让代码更干净。