0

I'm trying to implement a Config class that holds and keeps track of an arbitrary amount of Settings. I'd like to get and set the Settings like c = Config(<CONTAINER_OF_POSSIBLE_SETTINGS>); c.x = 1; print(c.foo) where x and foo are some Settings (or their name attribute) given to the Config during initialization. So I tried overwriting the __setattr__ and __getattr__ methods. But I'm running into an infinite recursion problem. Here is the bare-bones version of what I have so far:

#config.py

class Setting:
    def __init__(self, name, value):
        self.name = name
        self.value = value

    def __repr__(self) -> str:
        return f"Setting(name: {self.name}, value: {self.value})"


class Config:
    #_settings = {}
    def __init__(self, settings):
        self._settings = settings

    def __setattr__(self, name, value):
        try:
            self._settings[name].value = value
        except KeyError as err:
            try:
                return super(Config, self).__setattr__(name, value)
            except AttributeError:
                raise err

    def __getattr__(self, name):
        try:
            return self._settings[name].value
        except KeyError as err:
            try:
                return super(Config, self).__getattribute__(name)
            except AttributeError:
                raise err


class Interface:
    def __init__(self):
        settings = {
            s.name: s for s in [
                Setting("x", 1.),
                Setting("y", 2.),
            ]
        }
        self.config = Config(settings)


if __name__ == "__main__":
    i = Interface()
    print(i.config._settings)
    i.config.x = 9.
    print(i.config._settings)
    #print(Config._settings)

The idea was that __setattr__ would try to look up the attribute name in the self._settings dictionary or - if it can't find a Setting with the name - set the attribute the "usual way" by calling super().__setattr__. But running this results in:

$ python config.py
Traceback (most recent call last):
  File "config.py", line 52, in <module>
    i = Interface()
  File "config.py", line 48, in __init__
    self.config = Config(settings)
  File "config.py", line 15, in __init__
    self._settings = settings
  File "config.py", line 20, in __setattr__
    self._settings[name].value = value
  File "config.py", line 31, in __getattr__
    return self._settings[name].value
  File "config.py", line 31, in __getattr__
    return self._settings[name].value
  File "config.py", line 31, in __getattr__
    return self._settings[name].value
  [Previous line repeated 991 more times]
RecursionError: maximum recursion depth exceeded

From what I read in other threads, the problem is that self._settings in Config.__init__() calls __setattr__ which in turn calls __getattr__. And both methods try to access self._settings which causes the recursion. I'm not even getting to part where I try to access the dictionary's items.

One "hot-fix" I found was adding _settings as a class attribute to Config. After un-comenting the two lines in the code above the program yields:

$ python config.py
{'x': Setting(name: x, value: 1.0), 'y': Setting(name: y, value: 2.0)}
{'x': Setting(name: x, value: 9.0), 'y': Setting(name: y, value: 2.0)}
{}

But now I have this empty class attribute which is not what I want. And it shows that I don't fully understand initialization, __setattr__ , and __getattr__. What I'm trying to do is telling Config that the _settings attribute should be handled differently. Unfortunately, I can't just have all the Settings be class attributes of Config since they (can) depend on the Config and/or Interface class.

Is there any way to do what I'm trying to do? Thanks in advance!

nieswand
  • 33
  • 7
  • 1
    Replace `self._settings` by `self.__dict__['_settings']`. – Michael Butscher Apr 22 '23 at 12:04
  • That works, thanks! Another solution I thought of is having `Config` inherit from `dict`. That way, I don't even need `self._settings` and simple use `self` in `__setattr__` etc – nieswand Apr 22 '23 at 14:19

0 Answers0