带参数的装饰器本质是返回装饰器的函数,需三层嵌套:外层接收参数,中层接收函数,内层执行逻辑;多层装饰器按由下至上顺序应用,即f = dec1(dec2(f))。

带参数的装饰器本质是“装饰器工厂”
Python中带参数的装饰器,实际是一个返回装饰器的函数。它接收参数,内部定义真正的装饰器,再返回这个装饰器。关键在于三层嵌套:外层接收装饰器参数,中层接收被装饰函数,内层执行逻辑。
例如,实现一个可配置重试次数的装饰器:
def retry(max_attempts=3):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if i == max_attempts - 1:
raise e
return None
return wrapper
return decorator
<p>@retry(max_attempts=5)
def fetch_data():</p><h1>模拟可能失败的操作</h1><pre class='brush:python;toolbar:false;'>pass多层装饰器的执行顺序是“由下至上、由右至左”
当多个装饰器叠加使用时(如 @dec1
@dec2
def f():),Python先应用最靠近函数的装饰器(@dec2),再将结果传给上一层(@dec1)。即:f = dec1(dec2(f))。
常见组合场景与建议:
立即学习“Python免费学习笔记(深入)”;
- 日志 + 权限校验 + 缓存:通常把缓存放在最外层(减少重复计算),权限校验次之(早拦截非法调用),日志最内层或根据需要调整位置
- 注意副作用叠加:比如两个装饰器都修改了 func.__name__ 或 func.__doc__,需用 @functools.wraps(func) 保证元信息不丢失
- 调试时可在各层 wrapper 中加 print 观察调用流,验证执行顺序
带参数 + 多层装饰器的典型写法
组合使用时,每层带参装饰器都要保持三层结构;多层之间互不影响,但要注意参数作用域和闭包绑定。
示例:同时使用带参的日志装饰器和带参的超时控制:
def log_calls(level='INFO'):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[{level}] Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"[{level}] Done {func.__name__}")
return result
return wrapper
return decorator
<p>def timeout(seconds=5):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, *<em>kwargs):
import signal
def timeout_handler(signum, frame):
raise TimeoutError(f"{func.<strong>name</strong>} timed out after {seconds}s")
old_handler = signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(seconds)
try:
result = func(</em>args, **kwargs)
return result
finally:
signal.alarm(0)
signal.signal(signal.SIGALRM, old_handler)
return wrapper
return decorator</p><p>@log_calls(level='DEBUG')
@timeout(seconds=2)
def slow_function():
import time
time.sleep(3) # 将触发超时</p>实用技巧与避坑点
- 务必在每一层 wrapper 中使用 @functools.wraps(func),否则被装饰函数的 __name__、__doc__ 等会丢失
- 带参装饰器的参数不能是可变对象(如 list、dict)作为默认值,避免跨调用污染
- 如果装饰器需支持类方法、静态方法,注意 self 或 cls 参数位置,必要时用 inspect.signature 做适配
- 单元测试装饰器逻辑时,可直接调用中间层(如 retry(3)(my_func))绕过语法糖,更易断言








