Search code examples
pythonrecursionrepr

implementing __repr__ on a class, if try to add function members, get "RecursionError: maximum recursion depth exceeded"


I'm trying to implement __repr__ for a class, but when I want to access function members to add there value to __repr__ I get "maximum recursion depth exceeded".

I've noticed that if I remove all the class' functions the __repr__ works as expected.

I intend to do a workaround to skip the members which are function, but I want to understand why the recursion is happening.

Se below a simplified code.

class C:
    def __init__(self, a):
        self.a = a

    def getA(self):
        return self.a

    def __repr__(self):
        ret_str = self.__class__.__name__ + "\n"
        for v in dir(self):
            if v.startswith("__"):
                continue
            ret_str += "{} -> {}\n".format(v, getattr(self,v))
        return ret_str


c = C(1)
print(c)

here is the error

Traceback (most recent call last):
  File "test.py", line 18, in <module>
    print(c)
  File "test.py", line 13, in __repr__
    ret_str += "{} -> {}\n".format(v, getattr(self,v))
  File "test.py", line 13, in __repr__
    ret_str += "{} -> {}\n".format(v, getattr(self,v))
  File "test.py", line 13, in __repr__
    ret_str += "{} -> {}\n".format(v, getattr(self,v))
  [Previous line repeated 163 more times]
RecursionError: maximum recursion depth exceeded

Solution

  • Without your custom __repr__, formatting c.getA (i.e. what ret_str += "{} -> {}\n".format(v, getattr(self,v)) does when v is self.getA) results in

    <bound method C.getA of <__main__.C object at 0x1101f05c0>>
    

    IOW, the default __repr__ of a bound method requires repring the object it is bound to, resulting in unbound recursion. If your __repr__ had limited recursion, the result would be something like

    C
    getA -> <bound method C.getA of C
    getA -> <bound method C.getA of C
    getA -> <bound method C.getA of C
    getA -> <bound method C.getA of C
    ...
    

    which is likely not what you want anyway.

    You could add a guard that prevents this special behavior from happening on recursive calls:

    class C:
        def __init__(self, a):
            self.a = a
    
        def getA(self):
            return self.a
    
        def __repr__(self):
            try:
                if getattr(self, "_being_repred", False):
                    # Recursive call to __repr__, don't do special things
                    return super().__repr__()
                self._being_repred = True
                ret_str = self.__class__.__name__ + "\n"
                for v in dir(self):
                    if v.startswith("__"):
                        continue
                ret_str += "{} -> {}\n".format(v, getattr(self, v))
                return ret_str
            finally:
                self._being_repred = False
    
    
    c = C(1)
    print(c)
    

    The output is

    C
    getA -> <bound method C.getA of <__main__.C object at 0x1268c0890>>
    

    but that also gets weird if the "root call" wasn't directly to repr(c), e.g. print(c.getA) outputs

    <bound method C.getA of C
    getA -> <bound method C.getA of <__main__.C object at 0x1268c0800>>
    >
    

    EDIT: The fact bound methods' reprs repr the bound object stems from this code from 2001 (released in Python 2.2), complete with a /* XXX Shouldn't use repr() here! */ comment. The current version of that code still retains the same comment, but there's no longer a fallback behavior when repr fails.