0

I have this code:

from abc import ABC


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

    def parse(self, cfg_store: dict) -> None:
        # do something with the config dict and the cfg_store and setup attributes
        ...


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):
        """
        Adds relevant attributes from the member Config class to itself.
        This is done for all subclasses of ConfigurableComponent, so that the attributes are not required to be
        specified in two places (in the Config class and the component class).
        If there are significant differences between the Config attributes and the component attributes, they can be
        also specified in the component class, and then they will not be overwritten by the Config attributes.
        """
        super().__init_subclass__(**kwargs)
        config = getattr(cls, 'Config', None)
        if config:
            # copy non-callable, non-protected, non-private attributes from config to component class
            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:
                # ignore private attributes, methods, and members already defined in the class
                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')

It does not work. When subclassing the parent class ConfigurableComponent(ABC), the __init_subclass__ is called, but the config class variable does not contain the attributes defined in ExampleComponent.Config despite it showing it is the correct type. I expect the __init_subclass__ method to be called after the subclass is defined (including its members). Still, even though the members are here (there is a member named "Config" in the ExampleComponent class), they are not initialised - the Config class seems to be empty.

debugger printscr

So far I think the reason is that the member class gets fully initialised only after the owner class gets instantiated into an object, but I am not sure and can't seem to find the details in the documentation.

Does anybody have an idea how to make this code work so that I can: define attributes in the member class Config and get them added to the owning class automatically when subclassing the ConfigurableComponent class?

Adam Bajger
  • 523
  • 7
  • 20
  • 1
    `__init_subclass__` has nothing to do with abstract base classes per se. You can add that to any class to customize the definition of its subclasses. Keep in mind, `dir` is not meant to be definitive, exhaustive, or used in code. It's only meant to provide a list of "meaningful" attributes for human inspection in an interactive session. – chepner Feb 04 '23 at 16:14
  • I have finally found the culprit. I did not know the difference between attributes and annotations. Im gonna post te answer myself later. – Adam Bajger Feb 05 '23 at 16:46

0 Answers0