Carlos Miguel Colanta Carlos Miguel Colanta - 21 days ago 8
Python Question

maximum recursion depth exceeded when using a class descriptor with get and set

I have been playing with my code and I ended up with this:

class TestProperty:
def __init__(self,func):
self.func = func
def __set__(self,instance,value):
setattr(instance,self.func.__name__,value)
def __get__(self,instance,cls):
if instance is None:
return self
else:
return getattr(instance,self.func.__name__)


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

@TestProperty
def TestProp(self):
print('This line won\'t be printed')

p = John()
p.TestProp = 99
print(p.TestProp)


I'm trying to understand the behavior when creating a class descriptor and using them on methods instead of attributes. I am having a hard time understanding what's going on underneath and It would be really nice if someone can shed some light on me how did this end up as recursive error?

My initial guess is something like this:


  1. Method that is decorated with the descriptor is called

  2. Calls either
    __set__
    or
    __get__
    depending on how we accessed it.

  3. Descriptors attempts to set the value of the instance which calls which ends up mapping it back to step 1(error).



Can anyone explain to me in great detail how did this happen and how do I resolve this?

The code provided serves no purpose other than understanding the behavior of class descriptor.

Answer

Don't use getattr() and setattr(); you are triggering the descriptor again there! The descriptor handles all access to the TestProp name, using setattr() and getattr() just goes through the same path as p.TestProp would.

Set the attribute value directly in the instance.__dict__:

def __get__(self,instance,cls):
    if instance is None:
        return self
    try:
        return instance.__dict__[self.func.__name__]
    except KeyError:
        raise AttributeError(self.func.__name__)
def __set__(self,instance,value):
    instance.__dict__[self.func.__name__] = value

This works because you have a data descriptor; a data descriptor takes precedence over instance attributes. Access to p.TestProp continues to use the descriptor object on the class even though the name 'TestProp' exists in instance __dict__.

Comments