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')
问题原因
- 类型注解未被识别:
ExampleComponent.Config中的param1: int和param2: str是类型注解,仅存储在类的__annotations__字典中,并不会作为类属性出现在dir(config)的结果里,所以原代码的筛选逻辑无法捕获这些属性。 - 内部类初始化时机:
__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
相关产品推荐
相关产品推荐

