装饰器解决什么问题
先看一个真实痛点。项目里有一堆函数,现在要给每一个都加上「记录执行时间」的功能:
def get_user_list():
# ... 业务逻辑
pass
def create_order():
# ... 业务逻辑
pass
笨办法:每个函数内部都加两行计时代码。20 个函数改 20 处,以后要加「记录日志」再改 20 处——烦且容易漏。
装饰器的思路:写一个「包装机」,把原函数包一层,在不改原函数代码的前提下增加新功能。
@timer # 这一行就是装饰器,自动给函数加上计时功能
def get_user_list():
pass
原理:装饰器就是「函数套函数」
理解装饰器只需要知道两件事:
- 函数可以当参数传递,也可以当返回值返回
- 闭包:内部函数可以记住外部函数的变量
最简装饰器长这样:
import functools
import time
def timer(func):
"""装饰器:统计函数执行时间"""
@functools.wraps(func) # 保留原函数的名字和文档,好习惯
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) # 执行原函数
print(f"{func.__name__} 耗时 {time.time() - start:.3f} 秒")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
slow_function() # 输出:slow_function 耗时 1.002 秒
@timer 这行代码等价于 slow_function = timer(slow_function)——把原函数传进 timer,用返回的 wrapper 替换掉原来的名字。之后每次调用 slow_function(),实际执行的是包装后的 wrapper。
实战 1:日志记录
import functools
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
def log_calls(func):
"""记录函数调用的参数和返回值"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"调用 {func.__name__},args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
logging.info(f"{func.__name__} 返回 {result}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
add(3, 5)
# 输出两行日志:调用 add,args=(3, 5), kwargs={} / add 返回 8
实战 2:缓存(记忆化)
递归算斐波那契数列,不加缓存时 fib(35) 要算好几秒,因为重复计算了大量子问题。加个缓存装饰器:
import functools
def cache(func):
"""把算过的结果存起来,相同参数直接返回"""
saved = {}
@functools.wraps(func)
def wrapper(*args):
if args not in saved:
saved[args] = func(*args)
return saved[args]
return wrapper
@cache
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(100)) # 秒出结果
其实不用自己写:标准库 functools.lru_cache 就是现成的缓存装饰器,还带容量淘汰策略:
from functools import lru_cache
@lru_cache(maxsize=128)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
实战 3:权限校验(Web 开发天天用)
import functools
# 模拟当前登录用户
current_user = {"name": "张三", "role": "admin"}
def require_role(role):
"""带参数的装饰器:要求特定角色才能访问"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if current_user.get("role") != role:
raise PermissionError(f"需要 {role} 权限")
return func(*args, **kwargs)
return wrapper
return decorator
@require_role("admin")
def delete_all_data():
print("数据已删除")
delete_all_data() # 正常执行
# 把 current_user["role"] 改成 "guest" 再调用 → PermissionError: 需要 admin 权限
注意这里变成了三层函数嵌套:require_role("admin") 先拿到参数返回装饰器,装饰器再接收函数。规律:装饰器带参数,就多套一层。
Flask 里的 @app.route("/api")、登录校验 @login_required,全都是这个模式。
实战 4:重试机制
调用不稳定的网络接口时特别好用:
import functools
import time
def retry(times=3, delay=1):
"""失败自动重试"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"第 {attempt} 次失败:{e}")
if attempt == times:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(times=3, delay=2)
def call_unstable_api():
import random
if random.random() < 0.7: # 70% 概率失败,模拟不稳定接口
raise ConnectionError("网络超时")
return "成功"
实战 5:函数注册表(进阶但极其有用)
插件系统、命令分发器的标准写法:
COMMANDS = {} # 命令注册表
def register(name):
"""把函数注册到命令表"""
def decorator(func):
COMMANDS[name] = func
return func # 不改函数行为,只登记
return decorator
@register("hello")
def cmd_hello():
print("你好!")
@register("bye")
def cmd_bye():
print("再见!")
# 根据用户输入动态调用
user_input = "hello"
COMMANDS[user_input]() # 输出:你好!
新增命令只需要加新函数并装饰,调用处的分发逻辑一行不用改——这就是「开放封闭原则」。
避坑指南
1. 别忘了 @functools.wraps
不加的话,被装饰函数的 __name__、__doc__ 都会变成 wrapper 的,调试和文档生成时会错乱。
2. 装饰顺序很重要
@timer
@log_calls
def f(): ...
# 等价于 f = timer(log_calls(f))
# 执行顺序:先 timer 的包装,进去后先记日志再执行,出来后计时
3. 装饰器在导入时就执行
@register 这类装饰器在模块导入时就跑完了,不是调用时才跑。利用这个特性做自动注册,也要注意别在装饰器里写耗时操作拖慢启动。
写在最后
装饰器的学习路径:先会用 @lru_cache、@app.route 这些现成的 → 再读懂别人的装饰器 → 最后才会写自己的。别反过来。先把本文的 5 个例子各敲一遍,用到的场景出现时,你自然会长出「这里该用装饰器」的直觉。







评论 (0)
还没有评论,快来抢沙发吧~