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.
Walking through what your code does:
self.button = button
self.__setattr__(self, 'button', button)
hasattr(self.button, 'button')
self.button
, it runs self.__getattr__('button')
getattr(self.button, 'button')
self.button
, it runs self.__getattr__('button')
getattr(self.button, 'button')
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)