0

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?

RoachLord
  • 993
  • 1
  • 14
  • 28
  • Does `getattr(self.foo, name)` really make sense where `foo` will be a list according to the property? Which in turn means **"self.[2, 1]"** won't exist and so will try `__getattr__` again... – Dunes Jan 11 '19 at 13:25
  • 1
    What is `a.sort()` supposed to do? – L3viathan Jan 11 '19 at 13:26
  • `getattr(object, name[, default])` https://docs.python.org/3.7/library/functions.html#getattr – slybloty Jan 11 '19 at 13:28
  • @L3viathan I've edited the question. – RoachLord Jan 11 '19 at 13:28
  • Now that you added that `a.sort()` should sort `a._foo`: is `_foo` literally the name and the only list attached to the object? In that case, you don't need all this getattrness. – L3viathan Jan 11 '19 at 13:29

2 Answers2

1

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().

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
1

You need to set _foo to some sort of value initially, even if it's not the real one. Like this:

class A(object):
    _foo = None

    def __init__(self):
        pass

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

    @property
    def foo(self):
        if self._foo is None:
            self._foo = [2, 1]
        return self._foo 
Markus Unterwaditzer
  • 7,992
  • 32
  • 60
  • 1
    Defining `_foo` at the top-level makes it a class attribute, which will then be shadowed by the instance attribute created in `foo()`. While technically legal and seen in some code examples, this is considered a bad practice (it pollutes the class namespace for no good reason, makes the code harder to understand and can introduce subtle bugs). Instance attributes should be created in the initializer. – bruno desthuilliers Jan 11 '19 at 13:41
  • I don't know who considers this bad practice but feel free to edit – Markus Unterwaditzer Jan 11 '19 at 13:47