很多初学者看到代码里 @xxx 就一头雾水,这个@语法对应的就是装饰器(Decorator)。装饰器可以在不修改原函数源代码、不改变调用方式的前提下,动态给函数增加额外功能。装饰器 = 外套。穿上外套(使用装饰器),T恤本身没有改动,但是新增了保暖功能。调用T恤,依然是原来的调用方式。def decorator(func): # 内层wrapper:包装原函数 def wrapper(*args, **kwargs): print("【执行前】扩展逻辑") # 执行原始函数 res = func(*args, **kwargs) print("【执行后】扩展逻辑") return res return wrapperdef hello(name): print(f"Hello {name}") return "完成"# 手动包装hello = decorator(hello)result = hello("小明")print(result)
hello = decorator(hello) 就是核心:把原始函数传入装饰器,接收包装后的新函数。@装饰器名 等价于上面 func = decorator(func),简化代码。def decorator(func): def wrapper(*args, **kwargs): print("执行前") res = func(*args, **kwargs) print("执行后") return res return wrapper# 语法糖@decoratordef hello(name): print(f"Hello {name}")hello("小明")
⚠️关键点:*args, **kwargs 保证装饰器兼容任意参数的函数。def decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper@decoratordef add(a,b): """计算两数之和""" return a + bprint(add.__name__)print(add.__doc__)
装饰后,函数名、文档注释全部变成wrapper的信息,调试、生成文档都会出问题。✅ 解决方案:使用functools.wraps(工业开发强制规范)from functools import wrapsdef decorator(func): @wraps(func) # 复制原函数元信息 def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper@decoratordef add(a,b): """计算两数之和""" return a + bprint(add.__name__) # addprint(add.__doc__) # 计算两数之和
from functools import wraps# 第一层:接收装饰器参数def logger(level="INFO"): # 第二层:接收原始函数 def decorator(func): @wraps(func) # 第三层:包装逻辑 def wrapper(*args, **kwargs): print(f"[{level}] 调用函数:{func.__name__}") res = func(*args, **kwargs) print(f"[{level}] {func.__name__}执行结束") return res return wrapper return decorator# 使用方式,需要加括号传参@logger(level="DEBUG")def calc(x): return x * 2calc(10)
等价写法:calc = logger("DEBUG")(calc)def deco1(func): @wraps(func) def wrapper(*args, **kwargs): print("deco1 before") res = func(*args, **kwargs) print("deco1 after") return res return wrapperdef deco2(func): @wraps(func) def wrapper(*args, **kwargs): print("deco2 before") res = func(*args, **kwargs) print("deco2 after") return res return wrapper@deco1@deco2def test(): print("原始函数执行")test()
等价代码:test = deco1(deco2(test))import timefrom functools import wrapsdef timer(func): @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) cost = time.perf_counter() - start print(f"函数 {func.__name__} 耗时:{cost:.4f}s") return result return wrapper@timerdef slow_func(): time.sleep(0.5)slow_func()
from functools import wrapsdef retry(max_times=3): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): times = 0 while times < max_times: try: return func(*args, **kwargs) except Exception as e: times += 1 print(f"执行失败,重试{times}/{max_times}, err:{e}") raise Exception("多次重试仍然失败") return wrapper return decorator@retry(max_times=2)def connect_db(): raise ConnectionError("数据库连接失败")
from functools import wrapsdef login_required(func): @wraps(func) def wrapper(user, *args, **kwargs): if not user.get("is_login"): return {"code":401, "msg":"请登录"} return func(user, *args, **kwargs) return wrapper@login_requireddef get_user_info(user): return {"code":200, "data":user}print(get_user_info({"is_login":False}))
@property:把方法变成属性@staticmethod 静态方法@classmethod 类方法functools.lru_cache 缓存装饰器,递归优化神器
❌ 不适合:业务核心逻辑,不要把复杂业务全部塞进装饰器。