Search code examples
python-3.xinitsuper

Why do I need to super the __setattr__ function in init instead of simple assignment?


I made some code as follows:

class ButtonWrapper:
   def __init__(self, button: Button, attributes: ExtraButtonAttributes):
      self.button = button
      self.extraAttributes = attributes

   def __getattr__(self, key):
      return getattr(self.button, key)

   def __setattr__(self, key, value):
      if hasattr(self.button, key):
         setattr(self.button, key, value)
      else:
         setattr(self.extraAttributes, key, value)

But this gave infinite recursion on a getattr call.
I asked Copilot to fix this for me, and it did with

   def __init__(self, button: Button, attributes: ExtraButtonAttributes):
      super().__setattr__('button', button)
      super().__setattr__('extraAttributes', attributes)

This works! But that leaves me to question; why?
What is the difference between 'simple' assignment and 'super' assignment in the init that causes the second to not have infinite recursion?

Do note the Button object comes from a library and is not something I want to change, so I will use a Wrapper.


Solution

  • Walking through what your code does:

    1. You define self.button = button
    2. That runs self.__setattr__(self, 'button', button)
    3. The first step of that is hasattr(self.button, 'button')
    4. Because you are using self.button, it runs self.__getattr__('button')
    5. That function runs getattr(self.button, 'button')
    6. Because you are using self.button, it runs self.__getattr__('button')
    7. That function runs getattr(self.button, 'button')
    8. Repeat until recursion limit reached

    By using super().__setitem__ you are bypassing your custom code, and only setting the attribute without any other logic.

    As a general warning, you've got to be careful when overloading __getitem__ and __setitem__, and extra careful with __getattribute__, as it's extremely easy to run into unintended issues like this.

    To make your original code work with minimal edits, you can update __setitem__ to use super().

    def __setattr__(self, key, value):
        try:
            button = super().__getattribute__('button')
            extraAttributes = super().__getattribute__('extraAttributes')
        except AttributeError:
            super().__setattr__(key, value)
        else:
            if hasattr(button, key):
                setattr(button, key, value)
            else:
                setattr(extraAttributes, key, value)