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

如何获取跨文件抽象类的所有子类,并实现可扩展的评估器与指标架构

如何获取跨文件抽象类的所有子类,并实现可扩展的评估器与指标架构

嘿,看起来你已经摸到门道了!咱们先解决子类加载的问题,再聊聊如何设计一个能轻松扩展的评估器-指标架构,让团队新增功能时不用动核心代码。


一、解决跨文件子类动态加载的问题

你已经发现了,__subclasses__()返回空列表是因为子类还没被加载到内存里——Python只会在导入模块时才会执行模块里的类定义。这里给你两种更优雅的方案:

方案1:自动导入evaluators目录下的所有子类

手动在__init__.py里加新子类太麻烦,我们可以用pkgutil自动遍历目录导入所有模块:

修改evaluators/__init__.py:

import pkgutil
import sys

# 遍历当前目录下的所有非抽象类模块
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__, __name__ + '.'):
    # 跳过抽象类所在的evaluator模块
    if not is_pkg and module_name != f"{__name__}.evaluator":
        # 导入模块,这样子类就会被加载到内存
        __import__(module_name)
        # 把模块里的类加到__all__里(可选,方便外部导入)
        module = sys.modules[module_name]
        for attr_name in dir(module):
            attr = getattr(module, attr_name)
            if isinstance(attr, type) and issubclass(attr, module.Evaluator) and attr != module.Evaluator:
                __all__.append(attr_name)

# 初始化__all__
__all__ = []

这样以后新增评估器子类,只要放在evaluators目录下,不用改__init__.py就能被工厂类通过Evaluator.__subclasses__()获取到。

方案2:配置驱动的动态导入(更适合团队协作)

如果不想依赖自动导入,还可以用配置文件指定要加载的评估器类路径,让工厂类动态导入:

首先在你的evaluation_config里加一个配置项:

# 示例配置,可放在yaml/json或代码里
evaluation_config = {
    "enabled_evaluators": [
        "evaluation.evaluators.score_evaluator.ScoreEvaluator",
        "evaluation.evaluators.feedback_evaluator.FeedbackEvaluator"
    ]
}

然后修改EvaluatorFactory的_create_evaluators方法:

import importlib

class EvaluatorFactory:
    # ... 其他代码

    def _create_evaluators(self):
        evaluators = []
        # 从配置中读取要加载的评估器类路径
        for class_path in self.evaluation_config.get("enabled_evaluators", []):
            # 拆分模块名和类名
            module_name, class_name = class_path.rsplit('.', 1)
            # 导入模块
            module = importlib.import_module(module_name)
            # 获取类并实例化
            evaluator_cls = getattr(module, class_name)
            evaluators.append(evaluator_cls(self.framework_config, self.evaluation_config, **self.kwargs))
        return evaluators

这种方式的好处是:新增评估器不用改任何核心代码,只要在配置里加一行路径就行,完全符合开闭原则,团队协作时非常友好。


二、设计可扩展的评估器-指标关联架构

针对“特定指标属于特定评估器”的需求,我们可以用注册器模式或者配置映射来实现完全解耦的扩展方案:

方案1:用装饰器给评估器注册指标

首先确保你的Metric抽象类是标准的:

# metrics/metric.py
from abc import ABC, abstractmethod

class Metric(ABC):
    @abstractmethod
    def compute(self, predictions, targets):
        # 计算指标逻辑
        pass

    @property
    @abstractmethod
    def name(self):
        # 返回指标名称,比如"QWK"
        pass

然后给每个评估器类添加一个注册器:

# evaluators/score_evaluator.py
from evaluation.metrics.metric import Metric

class ScoreEvaluator(Evaluator):
    # 类属性,存储该评估器的所有指标实例
    _metrics = []

    @classmethod
    def register_metric(cls, metric_cls):
        """装饰器:用来注册指标到当前评估器"""
        if issubclass(metric_cls, Metric):
            cls._metrics.append(metric_cls())
        else:
            raise ValueError("必须注册Metric的子类")
        return metric_cls

    def __init__(self, evaluation_config, framework_config, **kwargs):
        super().__init__(evaluation_config, framework_config, **kwargs)
        self.score_generator = ScoreGenerator(llm=LLMClient(), vlm=VLMClient(), config=self.framework_config)
        # 初始化指标列表
        self.metrics = self._metrics.copy()

    async def __call__(self, *args, **kwargs):
        # 假设这里已经拿到预测结果和真实目标
        predictions, targets = await self._get_evaluation_data()
        
        # 计算所有指标
        results = {}
        for metric in self.metrics:
            results[metric.name] = metric.compute(predictions, targets)
        return results

现在新增指标时,只要用评估器的装饰器注册就行,完全不用改评估器代码:

# metrics/QWK.py
from evaluation.metrics.metric import Metric
from evaluation.evaluators.score_evaluator import ScoreEvaluator

@ScoreEvaluator.register_metric
class QWK(Metric):
    @property
    def name(self):
        return "QWK"

    def compute(self, predictions, targets):
        # 具体计算QWK的逻辑
        from sklearn.metrics import cohen_kappa_score
        return cohen_kappa_score(predictions, targets, weights="quadratic")

方案2:配置映射指标与评估器(更灵活)

如果不想在指标代码里硬绑定评估器,可以用配置来关联:

在evaluation_config里添加:

evaluation_config = {
    "evaluator_metrics": {
        "ScoreEvaluator": [
            "evaluation.metrics.QWK.QWK",
            "evaluation.metrics.PCC.PCC"
        ],
        "FeedbackEvaluator": [
            "evaluation.metrics.BLEU.BLEU"
        ]
    }
}

然后修改评估器的__init__方法来动态加载指标:

# evaluators/score_evaluator.py
import importlib

class ScoreEvaluator(Evaluator):
    def __init__(self, evaluation_config, framework_config, **kwargs):
        super().__init__(evaluation_config, framework_config, **kwargs)
        self.score_generator = ScoreGenerator(llm=LLMClient(), vlm=VLMClient(), config=self.framework_config)
        
        # 从配置加载当前评估器的指标
        metric_paths = evaluation_config.get("evaluator_metrics", {}).get(self.__class__.__name__, [])
        self.metrics = []
        for path in metric_paths:
            module_name, class_name = path.rsplit('.', 1)
            module = importlib.import_module(module_name)
            metric_cls = getattr(module, class_name)
            self.metrics.append(metric_cls())

    # ... 其他方法

这种方式的优势是:指标和评估器完全解耦,新增或修改关联关系只要改配置,不用动代码,适合复杂的多评估器多指标场景。


总结一下

  • 子类加载:用自动导入或配置驱动的动态导入,告别手动维护__init__.py的麻烦
  • 指标扩展:用注册器装饰器或配置映射,让新增指标/评估器时不用修改核心代码,完全符合开闭原则

这样你的架构就能轻松应对团队后续的扩展需求啦!

备注:内容来源于stack exchange,提问作者Dinosaur

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 11:09:50