You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何自动为类中所有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 05:22:47