Search code examples
pythongetattr

Overwriting __getattr__ causing recursion


I'm trying to create a wrapper class and then overwrite __getattr__ so that requests for attributes that are not in the wrapper class are passed through into the inner object. Below is a toy example where I used a list as the inner object -

class A(object):
    def __init__(self):
        pass

    def __getattr__(self, name):
        print 'HERE'
        return getattr(self.foo, name)

    @property
    def foo(self):
        try:
            return self._foo
        except:
            self._foo = [2, 1]
            return self._foo 

I would like to do something like

 a = A()
 a.sort()  # initializes and sorts _foo 
 print a.foo

which prints [1, 2]

Finally, I need _foo to initialized when foo is called for the first time or when someone tries to get an attribute from A() that is not in A. Unfortunately, I'm getting in some sort of recursion and HERE is being printed many times. Is what I want possible?


Solution

  • This

    return getattr(self.foo, name)
    

    is actually:

    foo = self.foo
    return getattr(foo, name)
    

    Now self.foo actually call this:

    try:
       return self._foo
    

    which, since your class defines __getattr__ and - at least one the first call to self.foo - doesn't have a _foo attribute, calls self.__getattr__("_foo"), which calls self.foo, which etc etc etc...

    Simple solution: make sure self._foo is defined before (ie in the initializer) instead of trying to define it in foo().

    You could also test for the existence of _foo in the instance's dict, but that will break inheritance if _foo is a computed attribute too - whether this is a problem or not depends on the context and concrete use case.

    @property
    def foo(self):
        if '_foo' not in self.__dict__:
            self._foo = [2, 1]
        return self._foo 
    

    NB: I assume you made foo a computed attribute to allow for lazy initialization of _foo, else it makes little sense. If that's the case, you can also, quite simply, creates _foo in the initializer with a sentinel value (usually None, unless None is a valid value for this attribute) and give it it's real value in foo().