如何修改Python装饰器以支持类方法与实例方法?
问题描述
我写了一个Preloader类,用于在事件触发时将数据预加载至缓存,希望通过装饰器结合事件键注册加载函数,触发事件时执行这些函数。
代码实现如下:
from collections import defaultdict class Preloader: def __init__(self): self.events = defaultdict(set) def register(self, event_key: str): print(f"Registering event: `{event_key}`") def decorator(func): print("Decorating function: ", func.__qualname__) def wrapper(*args, **kwargs): print("Wrapper called...") # 调用函数,将结果存入缓存并返回 return func(*args, **kwargs) self.events[event_key].add(wrapper) return wrapper return decorator def preload(self, event_key: str, *args, **kwargs): for func in self.events[event_key]: func(*args, **kwargs)
随后用register装饰了以下函数:
preloader = Preloader() class Product: @staticmethod @preloader.register("products") def get_products_static(): # 实际场景为数据库调用 print("Getting products from static method...") @classmethod @preloader.register("products") def get_products_cls(cls): # 实际场景为数据库调用 print("Getting products from class method...")
调用preloader.preload('products')时,类方法调用失败,报错:
TypeError: Product.get_products_cls() missing 1 required positional argument: 'cls'
想知道如何修改装饰器,使其支持类方法与实例方法?
问题原因
装饰器执行顺序导致了问题:@classmethod/@staticmethod是在@preloader.register之后执行的,也就是说你装饰的是原始函数,而非经过方法绑定处理后的类方法/静态方法对象。直接调用原始函数时,不会自动传入cls(类方法)或忽略参数(静态方法),因此报错。
解决方案
修改Preloader类,让它能自动识别函数类型并处理方法绑定逻辑,无需调整装饰器顺序即可兼容普通函数、静态方法、类方法和实例方法:
from collections import defaultdict import inspect import sys class Preloader: def __init__(self): self.events = defaultdict(list) def register(self, event_key: str): print(f"Registering event: `{event_key}`") def decorator(func): print("Decorating function: ", func.__qualname__) # 存储原始函数,不破坏原有装饰器逻辑 self.events[event_key].append(func) return func # 返回原始函数,让classmethod等装饰器正常工作 return decorator def preload(self, event_key: str, *args, **kwargs): for func in self.events[event_key]: # 已绑定的方法(如通过实例调用的方法)直接执行 if inspect.ismethod(func): func(*args, **kwargs) continue # 解析函数所属类信息 qualname_parts = func.__qualname__.split('.') if len(qualname_parts) < 2: # 普通函数直接执行 func(*args, **kwargs) continue # 获取函数所属的类 cls_name = qualname_parts[-2] module = sys.modules[func.__module__] cls = getattr(module, cls_name, None) if not cls: func(*args, **kwargs) continue # 从类中获取经过装饰器处理后的方法对象 class_attr = cls.__dict__.get(func.__name__) if isinstance(class_attr, classmethod): # 类方法:自动绑定类参数并执行 class_attr.__get__(None, cls)(*args, **kwargs) elif isinstance(class_attr, staticmethod): # 静态方法:直接执行 class_attr.__get__(None, cls)(*args, **kwargs) else: # 实例方法:需用户传入实例作为第一个参数 func(*args, **kwargs)
测试验证
调用preloader.preload('products')后,会正常输出:
Getting products from static method... Getting products from class method...
如果要支持实例方法,调用时传入实例即可:
class Product: @preloader.register("products") def get_products_instance(self): print(f"Getting products from instance {self}...") # 调用实例方法 p = Product() preloader.preload('products', p)
内容的提问来源于stack exchange,提问作者cmsommerville
相关产品推荐
相关产品推荐

