Search code examples
pythonstringooprecursionrepr

Can you return a __str__ representation of an object within the __repr__ method at recursion level 1?


I am looking for a way to implement a custom __repr__ function in Python, that handles recursion Pythonically, while maintaining readability.

I have two classes (Foo and Bar) that point to each other in their attributes. Of course, simply putting e.g.:

class Foo:
    def __init__(self, bar):
        self.bar = bar

    def __repr__(self):
        return f'Foo({self.bar})'

    @property
    def bar(self):
        return self._bar

    @bar.setter
    def bar(self, bar_instance):
        if bar_instance is not None:
            bar_instance._foo = self
            
        self._bar = bar_instance

class Bar:
    def __init__(self, foo=None):
        self.foo = None

    def __repr__(self):
        return f'Bar({self.foo})'

    @property
    def foo(self):
        return self._foo

    @foo.setter
    def foo(self, foo_instance):
        if foo_instance is not None: 
            foo_instance._bar = self
            
        self._foo = foo_instance

bar = Bar()
foo = Foo(bar)

Would result in a recursion error. Luckily, there's the @recursive_repr() from reprlib to the rescue. See my implementation below.

from reprlib import recursive_repr


class Foo:
    def __init__(self, bar):
        self.bar = bar

    @recursive_repr()
    def __repr__(self):
        return f'Foo({self.bar})'

    @property
    def bar(self):
        return self._bar

    @bar.setter
    def bar(self, bar_instance):
        if bar_instance is not None:
            bar_instance._foo = self
            
        self._bar = bar_instance

class Bar:
    def __init__(self, foo=None):
        self.foo = None

    @recursive_repr()
    def __repr__(self):
        return f'Bar({self.foo})'

    @property
    def foo(self):
        return self._foo

    @foo.setter
    def foo(self, foo_instance):
        if foo_instance is not None: 
            foo_instance._bar = self
            
        self._foo = foo_instance

bar = Bar()
foo = Foo(bar)

However, the custom implementation with fillvalue='...' doesn't improve readability much, in my opinion. Also, this representation of e.g. the foo instance - although I know this isn't required - doesn't make it very reproducible, necessarily.

>>> bar = Bar()
>>> foo = Foo(bar)
>>> foo
... Foo(Bar(...))

I am looking for an implementation where I can print a string representation of the foo instance, instead of printing the triple dots. I would want an output that'd be something like this: Foo(Bar(my_foo_object)) in which I'd be able to define my_foo_object as the return value of the Foo __str__ method.

Although in the above example this might not make a ton of sense, in my non-MWE it would provide a more intuitive perspective on the objects and their values.

In brief: is it possible to return a __str__ representation of an object within an objects recursive_repr at recursion level 1?


Solution

  • I am looking for an implementation where I can print a string representation of the foo instance, instead of printing the triple dots. I would want an output that'd be something like this: Foo(Bar(my_foo_object)) in which I'd be able to define my_foo_object as the return value of the Foo __str__ method.

    You need to define custom __str__ for both classes:

    def __str__(self):
        return "CustomBar"
    

    and in __repr__:

    def __repr__(self):
        return f'Foo({self.bar})'
    

    fstrings in Python default to __str__ you don't need to call str(self.bar). In this case, you don't need to decorate your __repr__ functions with @reprlib.recursive_repr as no recursion is taking place essentially when custom __str__ functions are present.

    Please note that you can override the Repr.fillvalue string which defaults to the ellipsis ... or reprlib.recursive_repr(fillvalue='somethingCustomHere').

    In brief: is it possible to return a __str__ representation of an object within an objects recursive_repr at recursion level 1?

    Definig __str__ would return the string representation, the string returned by __str__, without necessitating the need of recursive call to __repr__ and thus eliminating the use of recursive_repr to handle recursion.

    Hopefully this solves your problem.