如何自动为类中所有classmethod插入指定函数装饰器?
问题描述
我编写了一个装饰器,用于将函数包装在自定义上下文管理器中,通过跳过所有自动更新来提升代码库运行速度,计划将其应用于所有非测试自动更新功能的测试用例以节省时间。最初的装饰器代码如下:
@classmethod def no_autoupdates(cls): def decorator(fn): def wrapper(*args, **kwargs): coordinator = MaintainedModelCoordinator(auto_update_mode="disabled") with cls.custom_coordinator(coordinator): fn(*args, **kwargs) return wrapper return decorator
由于现有超500个测试用例,我希望直接将该装饰器应用于整个TestCase类。于是我将装饰器拆分为支持装饰函数或类的版本,筛选名称以test_或setUp开头的方法进行批量装饰:
@classmethod def no_autoupdates_function_decorator(cls, fn): disabled_coordinator = MaintainedModelCoordinator(auto_update_mode="disabled") def wrapper(*args, **kwargs): with cls.custom_coordinator(disabled_coordinator): fn(*args, **kwargs) return wrapper @classmethod def no_autoupdates(cls): def decorator(fn_or_class): if type(fn_or_class).__name__ == "function": return cls.no_autoupdates_function_decorator(fn_or_class) else: for name, fn in inspect.getmembers(fn_or_class, inspect.isfunction): if name.startswith("test_") or name.startswith("setUp"): setattr(fn_or_class, name, cls.no_autoupdates_function_decorator(fn)) return fn_or_class return decorator
但实际运行时发现,setUpTestData这类被@classmethod装饰的方法并未生效——手动测试时,只有将@classmethod放在我们的装饰器下方才有效,放在上方则无效。原因是@classmethod返回的是描述符对象而非函数,外层的函数装饰器无法正确作用到原始函数上。
我的疑问是:有没有办法自动装饰类中的classmethod,将函数装饰器有效插入到@classmethod与函数定义之间?
解决方案
要实现这个需求,核心是要获取classmethod包装的原始函数,装饰后再重新用classmethod包装回去,模拟手动调整装饰器顺序的逻辑。具体实现如下:
import inspect @classmethod def no_autoupdates_function_decorator(cls, fn): disabled_coordinator = MaintainedModelCoordinator(auto_update_mode="disabled") def wrapper(*args, **kwargs): with cls.custom_coordinator(disabled_coordinator): fn(*args, **kwargs) return wrapper @classmethod def no_autoupdates(cls): def decorator(fn_or_class): if inspect.isfunction(fn_or_class): return cls.no_autoupdates_function_decorator(fn_or_class) else: # 遍历类的所有属性,区分处理普通函数和classmethod for name, attr in inspect.getmembers(fn_or_class): if not (name.startswith("test_") or name.startswith("setUp")): continue # 处理普通实例方法 if inspect.isfunction(attr): decorated_fn = cls.no_autoupdates_function_decorator(attr) setattr(fn_or_class, name, decorated_fn) # 处理classmethod实例 elif isinstance(attr, classmethod): # 提取原始函数,装饰后重新包装为classmethod original_fn = attr.__func__ decorated_fn = cls.no_autoupdates_function_decorator(original_fn) setattr(fn_or_class, name, classmethod(decorated_fn)) return fn_or_class return decorator
关键逻辑说明
- 对于普通实例方法(如
test_xxx),直接装饰函数即可生效; - 对于
classmethod实例,通过其__func__属性获取被包装的原始函数,用我们的装饰器处理后,再重新用classmethod包装赋值回类属性,相当于手动实现了@classmethod在装饰器下方的顺序,确保上下文管理器能正确作用。
内容的提问来源于stack exchange,提问作者hepcat72
相关产品推荐
相关产品推荐

