Python装饰器带参数_多层装饰器实现方式

5次阅读

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

Python装饰器带参数_多层装饰器实现方式

带参数的装饰器本质是“装饰器工厂”

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__ 等会丢失
  • 带参装饰器的参数不能是可变对象(如 listdict)作为默认值,避免跨调用污染
  • 如果装饰器需支持类方法、静态方法,注意 selfcls 参数位置,必要时用 inspect.signature 做适配
  • 单元测试装饰器逻辑时,可直接调用中间层(如 retry(3)(my_func))绕过语法糖,更易断言

text=ZqhQzanResources