Marcel Preda
Marcel Preda

Reputation: 1205

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

Upvotes: 1

Views: 52

Answers (1)

AKX
AKX

Reputation: 168967

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.

Upvotes: 2

Related Questions