Search code examples
pythonpython-3.xrecursiongetattrgetattribute

python __getattribute__ RecursionError when returning class variable attribute


Why does Foo2 result in infinite recursion calling getattr of a class variable in __getattribute__, but Foo works fine making the same call in __getattr__? Any suggestions on how to get Foo2 to work?

class Foobar(object):
    def __init__(self):
        super().__init__()
        self.bar = 5

    def getbar(self):
        return self.bar


class Foo(object):
    def __init__(self):
        super().__init__()
        self.__foo = Foobar()

    def __getattr__(self, attr):
        return getattr(self.__foo, attr)


class Foo2(object):
    def __init__(self):
        super().__init__()
        self.__foo = Foobar()

    def __getattribute__(self, attr):
        try:
            return getattr(self.__foo, attr)
        except AttributeError:
            super().__getattribute__(attr)


if __name__ == '__main__':
    foo = Foo()
    foo2 = Foo2()
    print(foo.bar, foo.getbar())  # Works as expected
    try:
        print(foo2.bar, foo2.getbar())  # Doesn't work
    except RecursionError:
        print('Why does Foo2 result in RecursionError. How to fix?')

Setup: Windows 10, Python 3.7


Solution

  • The __getattribute__ method is called unconditionally to look up all attributes on an object, not only ones that don't exist (which is what __getattr__ does). When you do self.__foo in its implementation, you recurse, since __foo is another attribute that we're trying to look up on the object.

    To avoid this issue, you need to call your parent's __getattribute__ method to get all of your own attributes inside the __getattribute__ method:

    def __getattribute__(self, attr):
        try:
            return getattr(super().__getattribute__("_Foo__foo"), attr)
        except AttributeError:
            super().__getattribute__(attr)
    

    Note that I had to manually apply name mangling to the __foo attribute, because we need to pass the name as a string to super().__getattribute__. That probably suggests you shouldn't be doing the mangling in the first place. A name with a single leading underscore might be a better choice.