如何在Python类装饰器简写形式中为__call__传入*args和**kwargs
嘿,这个问题我之前踩过坑,类装饰器带参数确实比普通函数装饰器要绕一点点,但理清楚逻辑就很简单了,咱们一步步来拆解~
1. 带参数的类装饰器实现思路
带参数的类装饰器本质是一个装饰器工厂:先给它传参数,它返回一个真正能装饰函数的可调用对象(也就是装饰器类的实例)。直接上代码示例,结合例子讲更清楚:
import functools class MyDecorator: # __init__ 用来接收装饰器的参数,只在创建实例时执行一次 def __init__(self, prefix, suffix): self.prefix = prefix self.suffix = suffix print(f"装饰器实例初始化,参数:prefix={prefix}, suffix={suffix}") # __call__ 用来接收被装饰的函数,返回包装后的函数 def __call__(self, func): # 用 functools.wraps 保留原函数的元信息(比如函数名、文档字符串) @functools.wraps(func) def wrapper(*args, **kwargs): # 这里可以使用 __init__ 中保存的参数 print(f"[{self.prefix}] 准备执行函数 {func.__name__}") result = func(*args, **kwargs) print(f"[{self.suffix}] 函数执行完成,结果:{result}") return result return wrapper
2. 两种等价的使用方式
方式一:直接用 @装饰器类(参数) 装饰
这是最常用的写法,语法简洁直观:
@MyDecorator("日志前缀", "日志后缀") def add(a, b): """计算两个数的和""" return a + b # 调用函数时,会触发 wrapper 的逻辑 add(3, 5)
方式二:先创建装饰器实例,再用实例装饰
就是你提到的步骤:先创建实例(此时__init__执行,参数传入并保存),再用这个实例去装饰函数:
# 先创建装饰器实例,__init__ 在这里执行一次 my_decorator = MyDecorator("日志前缀", "日志后缀") # 用实例装饰函数,此时触发 __call__ 方法,接收 add 函数 @my_decorator def add(a, b): return a + b add(3, 5)
3. 关键逻辑梳理
- 当你写
@MyDecorator("日志前缀", "日志后缀")时,Python会先执行MyDecorator("日志前缀", "日志后缀"),创建一个装饰器实例(此时__init__被调用,参数被保存为实例属性); - 接着,这个实例的
__call__方法会被调用,参数是被装饰的add函数; __call__内部定义的wrapper函数就是最终替代原add的函数,调用add()其实是调用wrapper(),而wrapper可以随时访问实例中保存的装饰器参数。
4. 常见误区:区分带参数和不带参数的类装饰器
很多人会搞混这两种写法,这里做个简单对比:
- 不带参数的类装饰器:
__init__直接接收被装饰的函数,__call__负责包装调用逻辑:class SimpleDecorator: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print("执行前") result = self.func(*args, **kwargs) print("执行后") return result @SimpleDecorator def hello(): print("hello world") - 带参数的类装饰器:
__init__接收装饰器参数,__call__接收被装饰函数,这是核心区别。
5. 额外优化:保留原函数元信息
前面的示例中用到了functools.wraps(func),这个装饰器会把原函数的__name__、__doc__等元信息复制到wrapper上,避免调用add.__name__得到的是wrapper这种尴尬情况,建议养成习惯加上。
内容的提问来源于stack exchange,提问作者user17911
相关产品推荐
相关产品推荐

