3

I have the following code which comprises of a person class and a Manager class that delegates to a person class. I am using new style object(derived from object) and running python 2.7. I geth maximum recursion depth which I am unable to understand. I know the problem happens when setattr is used (when I commented this out in manager class, I see it works fine). why this recursion occurs and how to avoid it.

class Person(object):
    def __init__(self,name,job=None, pay=0):
        self.name=name
        self.job=job
        self.pay=pay

    def lastName(self):
        return self.name.split()[-1]

    def giveraise(self,percent):
        self.pay=int(self.pay*(1+percent))

    def __str__(self):
        return '[Person: %s, %s]' %(self.name, self.pay)


class Manager(object):
    def __init__(self,name,pay):
        self.person=Person(name,'mgr',pay)

    def giveraise(self, percent, bonus=0.10):
        self.person.giveraise(percent+bonus)

    def __getattr__(self,attr):
        print "calling getattr"
        return getattr(self.person, attr)

    def __setattr__(self,attr, value):
        print "calling setattr"
        self.person__dict__["attr"]=value

    def __str__(self):
        return str(self.person)


if __name__=="__main__":

    sue = Person("sue Jones","dev",10000)
    sue.giveraise(0.10)
    print sue
    print sue.lastName()

    print "- -"*25

    tom = Manager("Tom mandy", 50000)
    tom.giveraise(.10)
    print tom
    print tom.lastName()
brain storm
  • 30,124
  • 69
  • 225
  • 393

2 Answers2

7

The problem is that in Manager.__init__, you call __setattr__ to set the attribute person. But in __setattr__, you assume that self.person has already been set and has a well defined __dict__. In reality, it hasn't been set yet, so you end up calling __getattr__ which calls itself forever trying to get self.person.

One possible fix here is to bypass the initial call to __setattr__ in Manager.__init__:

class Manager(object):
    def __init__(self,name,pay):
        self.__dict__['person']=Person(name,'mgr',pay)

This should avoid the call to "__getattr__('person')" since self.person will already be set and normal attribute lookup will work1.

1__getattr__ is only called if normal attribute lookup fails

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • I changed __setattr__ to this: `def __setattr__(self,attr, value): print "calling setattr" return setattr(self.person,attr,value)` ,but still I get maximu recursion depth..why do I get this recursion depth problem..your solution works fine. but I want to understand why maximum recursion depth occurs – brain storm Dec 03 '13 at 21:55
  • @user1988876 -- The problem is because at that point, `self.person` doesn't exist yet. (It's never been set). So, that forces a call to `__getattr__` which tries to get `self.person` which forces another call to `__getattr__` which tries to get `self.person` which ... – mgilson Dec 03 '13 at 22:13
  • why does one call to __getattr__ forces another call to __getattr__, if the attribute is not found in the hierarchy, shouldnt it return `none` or something similar – brain storm Dec 03 '13 at 22:21
  • @user1988876 -- because `__getattr__` has the line `self.person` in it. That calls `__getattr__` (again!) if the attribute isn't found by the normal means. There is no implicit returning of `None` done here. Why would you expect there to be? – mgilson Dec 03 '13 at 22:27
2

The answer of mgilson is almost correct, however, in the __setattr__ function of class manager a dot is missing in between person__dict__:

def __setattr__(self,attr, value):
        print "calling setattr"
        self.person__dict__["attr"]=value

When __init__ tries to set self.person, the __setattr__ queries for a non-existent attribute person__dict__, the Managers' __getattr__ function gets called. It in turn queries for self.person, which does not exist at this stage, and at this stage you get a __getattr__ recursion.

picobyte
  • 21
  • 1