装饰器带参数时必须三层嵌套:第一层接收装饰器参数并校验,默认值处理也在此层;第二层接收被装饰函数,返回第三层;第三层接收原函数参数并执行逻辑,且必须用@functools.wraps修饰以保留元信息。

装饰器带参数时为什么必须是三层嵌套?
因为 Python 解释器在 @ 语法糖解析阶段,要求装饰器表达式本身必须返回一个可调用对象(即第二层闭包),而这个可调用对象要接收被装饰函数作为参数。所以「传参」和「装饰」必须解耦:第一层接收自定义参数,第二层接收函数,第三层才是实际执行逻辑的包装函数。
常见错误是只写两层,比如直接在 decorator(param) 里 return wrapper,结果报 TypeError: 'function' object is not callable——本质是 @decorator("x") 被求值后没返回可调用对象。
- 第一层(最外):接收装饰器参数,返回第二层函数
- 第二层(中间):接收被装饰函数
func,返回第三层函数 - 第三层(最内):接收原函数的参数,执行增强逻辑 + 调用
func
如何正确实现带参数的装饰器并保留原函数元信息?
不加 @functools.wraps(func) 会导致 __name__、__doc__ 等丢失,调试和反射会出问题。三层结构中,@functools.wraps 必须作用在第三层函数上,而不是第二层。
from functools import wraps
<p>def log_calls(level="INFO"):
def decorator(func):
@wraps(func) # ← 这里必须放第三层定义前
def wrapper(*args, *<em>kwargs):
print(f"[{level}] Calling {func.<strong>name</strong>}")
return func(</em>args, **kwargs)
return wrapper
return decorator
</p>
如果把 @wraps 错放在第二层(即 decorator 函数上),它包裹的是 func,但 func 不是 wrapper,毫无意义。
参数校验和默认值处理该放在哪一层?
校验逻辑应放在第一层(参数接收层),避免每次调用函数都重复检查。比如限制 level 只能是 "DEBUG"/"INFO"/"ERROR",就该在 log_calls(level) 内做,而不是塞进 wrapper。
- 合法参数 → 正常返回第二层函数
- 非法参数 → 立即抛出
ValueError,而不是等到运行时 - 默认值推荐用
None占位,在第一层显式判断,别依赖**kwargs动态兜底
否则会出现:装饰器看似生效了,但参数无效,日志级别始终是默认值,排查困难。
为什么有时候装饰器“不生效”?常见漏点有哪些?
最典型的是忘记调用最外层函数:写了 @log_calls 却没加括号,等价于 @log_calls(None),而很多装饰器第一层不接受 None,直接报错或静默失败。
-
@log_calls❌ —— 传了None,可能触发异常或逻辑跳过 -
@log_calls()✅ —— 显式调用,得到第二层函数 -
@log_calls(level="WARN")✅ —— 带关键字参数也必须加括号 - 类方法上使用时,注意
self是第一个参数,wrapper必须原样透传
还有人把参数写成位置参数但定义为关键字形参,比如 @log_calls("WARN") 却定义 def log_calls(*, level="INFO"),会直接报 TypeError。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











