Python中类实现的方法装饰器为何必须加括号调用?
类装饰器带/不带括号的工作原理及无参实现方案
核心原理
Python装饰器本质是语法糖,两种写法的执行逻辑完全不同:
- 写
@装饰器(不带括号)时,Python会直接把被装饰的函数/方法作为第一个参数传给装饰器本身,返回值替换原函数 - 写
@装饰器()(带括号)时,Python会先执行装饰器()得到一个可调用对象,再把被装饰的函数/方法传给这个可调用对象,返回值替换原函数
你当前遇到报错的原因是:PrintLog是类装饰器,不带括号写@PrintLog时,Python会把被装饰的baz方法直接传给PrintLog的__init__构造方法,你初始版本的__init__没有接收额外参数的逻辑,就会抛出参数不匹配的错误。带括号写@PrintLog()时,会先实例化PrintLog对象,再把baz方法传给实例的__call__方法,逻辑匹配所以能正常运行。
实现无括号无参类装饰器的方案
方案1:纯无括号无参场景适配
如果不需要给装饰器传任何参数,把接收被装饰函数的逻辑迁移到__init__,__call__负责执行包裹逻辑即可:
from functools import wraps class PrintLog(object): def __init__(self, func): # 不带括号调用时,被装饰函数直接传入__init__ wraps(func)(self) self.func = func def __call__(self, *args, **kwargs): # 调用被装饰方法时触发该逻辑 print('I am a log') return self.func(*args, **kwargs) class foo(object): def __init__(self, rs: str) -> None: self.ter = rs @PrintLog # 无需加括号即可正常使用 def baz(self) -> None: print('inside baz') bar = foo('2') print('running bar.baz()') bar.baz()
运行结果和你之前带括号的版本完全一致。
方案2:兼容带/不带括号两种写法
如果后续可能需要给装饰器加自定义参数,可以做兼容适配,两种写法都支持:
from functools import wraps class PrintLog(object): def __init__(self, func=None, *, log_msg="I am a log"): self.log_msg = log_msg if func is not None: # 无括号调用逻辑 wraps(func)(self) self.func = func def __call__(self, *args, **kwargs): if hasattr(self, 'func'): # 无括号场景:直接执行包裹逻辑 print(self.log_msg) return self.func(*args, **kwargs) else: # 带括号场景:接收被装饰函数 func = args[0] wraps(func)(self) self.func = func return self class foo(object): def __init__(self, rs: str) -> None: self.ter = rs @PrintLog # 无括号可正常使用 def baz(self) -> None: print('inside baz') @PrintLog(log_msg="custom log") # 带自定义参数也可正常使用 def baz2(self) -> None: print('inside baz2') bar = foo('2') bar.baz() bar.baz2()
内容的提问来源于stack exchange,提问作者Codeman
相关产品推荐
相关产品推荐

