1

So I've recently come up with a botched abomination of a class while I was trying to implement some sort of DotDict that would:

  • Enable key-value dot-access.
  • Accept a defaults Namespace/Dataclass as an extra argument containing default values.
  • Perform a modified get() when dot-accessed, by checking if a default value can be found in defaults, else return None.

Also, the class should recursively convert any dict found in its values upon initialisation and for each new value into a Dotdict.

Here's how it looks:

class DotDict(dict):

    def __init__(self, base_dict=None, defaults=None):

        self.defaults = defaults if defaults is not None else SimpleNamespace()
        base_dict = base_dict if base_dict is not None else {}
        super().__init__(**base_dict)
        for key, value in base_dict.items():
            self[key] = self._convert(value)

    def __getattr__(self, key):

        if key in self:
            return self[key]

        elif hasattr(self.defaults, key):
            return getattr(self.defaults, key)

        else:
            return None

    def __setattr__(self, key, value):

        self[key] = value

    def __delattr__(self, key):

        try:
            del self[key]

        except KeyError as error:
            raise AttributeError(error)

    def _convert(self, element):

        if isinstance(element, dict):
            return DotDict(
                {key: self._convert(value) for key, value in element.items()},
                defaults=self.defaults,
            )

        elif isinstance(element, list):
            return [self._convert(subelement) for subelement in element]

        else:
            return element

Although it probably goes against all common sense and about a dozen best practices, the thing sort of works - except for one detail: I have yet to find a way to assign de default attribute without adding it to the instances' items(). This makes sense, as the __setattr__ method is immediately overridden by its dot-access version.

Edited for clarity

The above paragraph was so terribly phrased as to, in effect, ask the wrong question. I am aware there are a few ways to assign attributes without resorting to the classe's own __setattr__, as kindly pointed out by user2357112 supports Monica, which would indeed solve this specific issue.

The code described above was meant as an illustration of the process that led me to ask myself the actual question of this post. Reading the whole thing with fresh eyes now makes me realise that most of the original question's details were at best unnecessary, and likely confusing. I'll leave it up for transparency, but if I was to rewrite the question from scratch, it would simply be:

Is there any way to overwrite any class method after the instance has been initialised?

ohno
  • 31
  • 2
  • Does this answer your question? [Clean way to disable \`\_\_setattr\_\_\` until after initialization](https://stackoverflow.com/questions/12998926/clean-way-to-disable-setattr-until-after-initialization) – Jorge Luis Apr 13 '23 at 13:34

1 Answers1

2

If you want to use the standard __setattr__, just use the standard __setattr__:

object.__setattr__(self, 'defaults', defaults)

Other options including calling it through super with super().__setattr__('defaults', defaults) or sticking the attribute into self.__dict__ manually with self.__dict__['defaults'] = defaults.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • Hi, and thanks for your answer. It made me realise my question was so poorly phrased as to actually ask for the wrong info. I've edited the original question for clarity. – ohno May 15 '21 at 15:08