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

Python中利用ABC的__init_subclass__自动复制成员类属性到子类

问题:子类继承ConfigurableComponent时,无法自动同步内部Config类的属性

我写了一段Python代码,想实现让子类继承ConfigurableComponent时,自动把内部Config类中定义的属性同步到子类本身,但代码无法正常工作。当子类化ConfigurableComponent时,__init_subclass__方法会被调用,但Config类变量里找不到ExampleComponent.Config中定义的属性,尽管它的类型是正确的。我原本以为__init_subclass__会在子类(包括其内部成员)定义完成后执行,但即使ExampleComponent里有Config成员,这个内部类的属性看起来是空的。我猜测可能是内部类要等到所属类实例化后才会完全初始化,但不确定,也没找到相关文档细节。

需要修改代码实现:在内部Config类中定义属性后,子类继承ConfigurableComponent时能自动将这些属性添加到子类本身。

原代码

from abc import ABC


class ConfigProto(ABC):
    def __init__(self, config_dict):
        self._config_dict = config_dict

    def parse(self, cfg_store: dict) -> None:
        # 处理配置字典和cfg_store,设置属性
        ...


class ConfigurableComponent(ABC):
    class Config(ConfigProto):
        ...

    def __init__(self, parsed_config: Config):
        for key, value in parsed_config.__dict__.items():
            setattr(self, key, value)

    def __init_subclass__(cls, **kwargs):
        """
        将内部Config类的相关属性添加到子类本身。
        所有ConfigurableComponent的子类都会执行此逻辑,避免在Config类和组件类中重复定义属性。
        如果Config属性和组件属性有显著差异,可以在组件类中单独定义,此时不会被Config属性覆盖。
        """
        super().__init_subclass__(**kwargs)
        config = getattr(cls, 'Config', None)
        if config:
            # 从Config类复制非可调用、非保护、非私有属性到组件类
            conf_class_attributes = [attr_ for attr_ in dir(config)
                                     if not attr_.startswith('_')
                                     and not callable(getattr(config, attr_))]
            for attr_name in conf_class_attributes:
                # 忽略私有属性、方法以及子类已定义的成员
                if not attr_name.startswith('_') \
                        and not callable(getattr(config, attr_name)) \
                        and not hasattr(cls, attr_name):
                    setattr(cls, attr_name, getattr(config, attr_name))


class ExampleComponent(ConfigurableComponent):
    class Config(ConfigProto):
        param1: int
        param2: str

        def parse(self, cfg_store: dict) -> None:
            ...


example_component = ExampleComponent(ExampleComponent.Config(config_dict={'param1': 1, 'param2': 'test'}))
assert hasattr(example_component, 'param1')

问题原因

  1. 类型注解未被识别:ExampleComponent.Config中的param1: int和param2: str是类型注解,仅存储在类的__annotations__字典中,并不会作为类属性出现在dir(config)的结果里,所以原代码的筛选逻辑无法捕获这些属性。
  2. 内部类初始化时机:__init_subclass__确实是在子类定义完成时执行,内部Config类此时已经完全定义,只是原代码的属性获取方式不对,导致看不到类型注解属性。

修改方案

调整__init_subclass__中的属性获取逻辑,同时处理类型注解和有默认值的类属性,确保能捕获Config类中定义的所有需要同步的属性:

from abc import ABC


class ConfigProto(ABC):
    def __init__(self, config_dict):
        self._config_dict = config_dict

    def parse(self, cfg_store: dict) -> None:
        # 处理配置字典和cfg_store,设置属性
        ...


class ConfigurableComponent(ABC):
    class Config(ConfigProto):
        ...

    def __init__(self, parsed_config: Config):
        for key, value in parsed_config.__dict__.items():
            setattr(self, key, value)

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        config = getattr(cls, 'Config', None)
        if not config:
            return
        
        # 收集所有需要同步的属性:类型注解 + 非可调用的类属性(排除私有/保护成员)
        target_attrs = set()
        # 加入类型注解中的属性名
        if hasattr(config, '__annotations__'):
            target_attrs.update(config.__annotations__.keys())
        # 加入类中已定义的非可调用、非私有/保护属性
        for attr_name in dir(config):
            if not attr_name.startswith('_') and not callable(getattr(config, attr_name)):
                target_attrs.add(attr_name)
        
        # 将属性同步到子类,跳过子类已有的属性
        for attr_name in target_attrs:
            if not hasattr(cls, attr_name):
                # 如果Config类中有该属性的实际值就用这个值;如果只有类型注解,就设为None
                attr_value = getattr(config, attr_name, None)
                setattr(cls, attr_name, attr_value)


class ExampleComponent(ConfigurableComponent):
    class Config(ConfigProto):
        param1: int
        param2: str
        # 测试带默认值的属性
        param3: bool = True

        def parse(self, cfg_store: dict) -> None:
            ...


example_component = ExampleComponent(ExampleComponent.Config(config_dict={'param1': 1, 'param2': 'test'}))
assert hasattr(example_component, 'param1')
assert hasattr(ExampleComponent, 'param1')
assert ExampleComponent.param3 is True

关键改动说明

  • 同时从__annotations__中读取类型注解定义的属性,确保即使没有赋值的类型注解属性也能被捕获。
  • 合并类中已有的非可调用、非私有/保护属性,覆盖有默认值的场景。
  • 简化属性判断逻辑,避免重复检查,提升可读性。

内容的提问来源于stack exchange,提问作者Adam Bajger

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 14:35:40