发布于2026-07-14 阅读(0)
扫一扫,手机访问
带参数的装饰器,本质上是一个“装饰器工厂”。你写 @log(level="DEBUG") 的时候,Python 先调用 log(level="DEBUG"),拿到一个真正的装饰器,再拿这个装饰器去包装下面的函数。所以,三层嵌套是绕不开的:外层接收参数,中层接收函数,内层执行实际逻辑。漏掉任何一层,或者把 @wraps 放错位置,都会报错。下面咱们就把这个坑踩实了,再填上。
因为带参数的装饰器本质是“装饰器工厂”,不是直接作用于函数的装饰器。你写 @log(level="DEBUG") 时,Python 实际调用的是 log(level="DEBUG"),它必须返回一个可调用对象(通常是闭包),这个对象才真正接收被装饰的函数。
常见错误是忘了多包一层函数,比如写成:
def log(level): print(f"Logging at {level}") return lambda func: func # ❌ 这里只打印一次,且没保存 level 到闭包中
结果是装饰器初始化时就执行了日志输出,且所有被装饰函数共享同一份 level 值(实际还没绑定)。
decorator)不能直接执行逻辑,它是被 Python 自动调用的入口functools.wraps 要用在最内层 wrapper 上,否则 func.__name__ 会变成 wrapper核心是严格遵循“参数 → 函数 → 调用”顺序,并用 functools.wraps 修复签名。下面是最小可靠模板:
from functools import wraps
def retry(max_attempts=3, delay=1):
def decorator(func):
@wraps(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
time.sleep(delay)
return None
return wrapper
return decorator
注意:@wraps(func) 必须放在 wrapper 定义之后、return wrapper 之前;decorator 是闭包,能访问外层 max_attempts 和 delay。
@retry(max_attempts=2),即使参数全用默认值也不能省略 ()@retry),需在 decorator 层判断入参类型——但容易出错,不建议初学尝试args 和 kwargs 要原样透传,否则会丢参数或类型错误当参数多、逻辑重(比如要记录调用次数、缓存状态、管理连接池),类写法更清晰。关键是实现 __call__ 并在 __init__ 存参:
class cached:
def __init__(self, timeout=300):
self.timeout = timeout
self.cache = {}
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
key = (args, tuple(sorted(kwargs.items())))
now = time.time()
if key in self.cache:
result, timestamp = self.cache[key]
if now - timestamp < self.timeout:
return result
result = func(*args, **kwargs)
self.cache[key] = (result, now)
return result
return wrapper
类的优势在于状态可封装(如 self.cache),但要注意:每个 @cached() 调用都会新建一个实例,self.cache 不跨装饰器共享。
__call__,且返回 wrapper,不能在 __init__ 里直接返回函数__init__ 中做耗时操作,比如打开文件、连数据库——那会发生在装饰阶段,而非运行阶段self别只看函数是否运行,重点查三件事:参数是否传进来了、wrapper 是否被调用、原函数属性是否保留。
加一行 print(f"Using level={level}") 在外层函数里,启动时就会打印;在 wrapper 里加 print("Running..."),每次调用才触发。再检查:
def hello(): return "hi" decorated = retry(max_attempts=1)(hello) print(decorated.__name__) # 应该是 "hello",不是 "wrapper"
__name__ 是 wrapper,说明漏了 @wraps 或位置不对@retry(没括号)导致 Python 把函数当参数传给了外层help(decorated) 看 docstring 是否保留,这是 @wraps 最容易被忽略的验证点复杂装饰器的坑往往不在语法,而在闭包变量捕获时机和作用域混淆——写完务必用不同参数组合跑几遍真实调用。
上一篇:Debian PHP部署流程概述
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8