如何以类的形式实现带参数的Python装饰器?
你遇到的这个问题我之前也碰到过——用类写带参数装饰器时,在类方法上用就会搞混self的指向,确实挺头疼的。核心问题在于你原来的写法返回的是装饰器类的实例方法,当Python处理类方法装饰时,会把装饰器实例当成第一个参数(也就是原方法的self)传进去,直接打乱了参数顺序。不过有几种靠谱的类实现方案能解决这个问题,我给你详细说说:
方案一:在包装函数中动态识别参数位置
这种思路是在__call__里返回一个通用的包装函数,手动区分普通函数和类方法/实例方法的参数结构,确保能正确拿到req和resp,同时完整传递原函数的所有参数:
import jsonschema import functools from some_module import _log, errors # 替换成你的实际依赖 class jsonschema_validate(object): def __init__(self, schema): self._schema = schema def __call__(self, fn): @functools.wraps(fn) # 保留原函数的元信息(名称、文档字符串等) def wrapper(*args, **kwargs): # 识别req和resp的位置:如果第一个参数是类/实例,就往后偏移一位 req_idx = 1 if (len(args) > 0 and hasattr(args[0], '__class__') and not callable(args[0])) else 0 resp_idx = req_idx + 1 # 从位置参数或关键字参数中获取req和resp req = args[req_idx] if req_idx < len(args) else kwargs.get('req') resp = args[resp_idx] if resp_idx < len(args) else kwargs.get('resp') if not req or not resp: raise TypeError("Missing required parameters: req and resp") # 执行JSON Schema校验 try: jsonschema.validate(req.media, self._schema, format_checker=jsonschema.FormatChecker()) except jsonschema.ValidationError as e: _log.exception('Validation failed: %r', e) raise errors.HTTPBadRequest('Bad request') # 调用原函数,传递所有原始参数 return fn(*args, **kwargs) return wrapper
这个方案的好处是兼容性强,不管装饰普通函数还是类方法/实例方法都能正常工作,因为它会自动适配参数结构。
方案二:利用Python描述器协议(更优雅的适配类方法)
如果你主要是用来装饰类方法,那可以利用Python的描述器协议来解决绑定问题。当装饰器类实现了__get__方法时,Python在访问类的实例方法时会自动调用这个方法,帮我们正确绑定原类的实例:
import jsonschema import functools from some_module import _log, errors class jsonschema_validate(object): def __init__(self, schema): self._schema = schema self._fn = None def __call__(self, fn): self._fn = fn return self # 返回装饰器实例本身,而非包装函数 def __get__(self, instance, owner): # 当访问类的实例方法时,Python会调用这个方法 # instance是原类的实例(比如MyResource的实例),owner是原类本身 @functools.wraps(self._fn) def wrapper(*args, **kwargs): # 提取req和resp(此时args的第一个参数就是req,因为instance已经被绑定) req = args[0] if args else kwargs.get('req') resp = args[1] if len(args)>=2 else kwargs.get('resp') try: jsonschema.validate(req.media, self._schema, format_checker=jsonschema.FormatChecker()) except jsonschema.ValidationError as e: _log.exception('Validation failed: %r', e) raise errors.HTTPBadRequest('Bad request') # 调用原函数时,把原类实例作为第一个参数传入 return self._fn(instance, *args, **kwargs) return wrapper
使用这个装饰器的方式和你原来的写法完全一样:
class MyResource: @jsonschema_validate(my_schema) def post(self, req, resp): # 业务逻辑代码 pass
当你调用my_resource_instance.post(req, resp)时,Python会先调用jsonschema_validate实例的__get__方法,拿到绑定了my_resource_instance的wrapper函数,此时wrapper接收的参数是(req, resp),调用原post方法时会自动把my_resource_instance作为第一个参数(也就是原方法的self)传进去,完美解决了参数错位的问题。
为什么原来的写法会出错?
你原来的代码中,__call__返回的是self._decorator——这是一个绑定到jsonschema_validate实例的方法。当装饰类方法时,Python会把装饰器实例当成第一个参数(也就是原方法的self)传给_decorator,导致原方法的参数顺序完全混乱:本来应该是(my_resource_instance, req, resp, ...),结果变成了(jsonschema_validate_instance, req, resp, ...),原方法需要的self(MyResource实例)根本没传,自然会报错缺少resp参数。
内容的提问来源于stack exchange,提问作者Anthony

