带参数的装饰器必须是三层函数,因为@decorator(arg)先执行decorator(arg)并要求其返回一个可调用的真正装饰器;第一层收装饰器参数,第二层收被装饰函数,第三层收调用参数并执行逻辑,缺一层就会报TypeError。

带参数的装饰器为什么必须是三层函数?
因为 Python 解释器在遇到 @decorator(arg) 语法时,会先调用 decorator(arg),期望它返回一个「真正的装饰器」(即接收函数并返回包装后函数的可调用对象)。如果只写两层,decorator(arg) 返回的就不是可调用对象,会直接报 TypeError: 'function' object is not callable。
常见错误现象:把参数逻辑塞进第二层,导致被装饰函数一调用就报错,或者参数根本没生效。
- 第一层(最外):接收装饰器参数,比如
log_level、retries - 第二层:接收被装饰的函数
func - 第三层:返回实际执行逻辑的闭包(通常叫
wrapper)
如何正确传递和使用装饰器参数?
参数在第一层捕获后,需通过闭包传入第三层。不能在 wrapper 里直接引用外层局部变量(比如误写成 print(level) 而没定义),否则运行时报 UnboundLocalError 或取到错误值。
典型场景:日志开关、重试次数、超时控制。例如下面这个带 prefix 参数的日志装饰器:
立即学习“Python免费学习笔记(深入)”;
def add_prefix(prefix):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"[{prefix}] Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
<p>@add_prefix("DEBUG")
def greet(name):
return f"Hello, {name}"</p><p>greet("Alice") # 输出: [DEBUG] Calling greet
用 functools.wraps 保留原函数元信息
不加 @functools.wraps(func) 会导致被装饰函数的 __name__、__doc__、__module__ 全部变成 wrapper 的,这对调试、IDE 提示、文档生成(如 Sphinx)都是硬伤。
必须加在第三层的 wrapper 上,且括号里传的是第二层接收到的 func:
- 错误写法:
@wraps(wrapper)或漏掉@wraps - 正确写法:
@wraps(func),放在def wrapper(...):之前 - 别忘了
import functools
否则你查 greet.__name__ 会得到 'wrapper' 而不是 'greet'。
装饰器参数支持默认值或类型检查怎么办?
参数校验逻辑应放在第一层。比如要求 retries 是正整数,就在 def retry(retries=3): 之后立刻做判断,而不是拖到 wrapper 里——那样每次调用都被检查,纯属浪费。
容易被忽略的点:当参数本身是可选(比如 None)或需要动态计算时,第一层函数的参数签名要足够灵活,避免因缺省值引发意外绑定。例如:
def cache(ttl=None):
if ttl is not None and not isinstance(ttl, (int, float)) or ttl < 0:
raise ValueError("ttl must be non-negative number")
def decorator(func):
# ...
真正复杂的参数处理(如解析配置字典、连接数据库)也建议放第一层完成,确保只执行一次,而不是每次调用都重复初始化。


















