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?

Sam
  • 305
  • 1
  • 8
  • You could use the [source](https://github.com/python/cpython/blob/3.11/Lib/reprlib.py) of `recursive_repr` as base for your own implementation. Mainly the `return fillvalue` must be changed. – Michael Butscher Feb 06 '23 at 10:44
  • @Sam Your first code will not be recursive. Where does the `self.bar` attribute come from? – GIZ Feb 06 '23 at 10:48
  • @GIZ: Oh, I see what you mean. There is no recursion, but I think you mean: `self.foo`. That is only set to `None` in the above code. – quamrana Feb 06 '23 at 10:51
  • @quamrana The first code OP posted doesn't result in a recursion error. `f'Foo({self.bar})'` will rather raise `ArributeError` exception. – GIZ Feb 06 '23 at 10:59
  • @GIZ Apologies, apparently this wasn't clear. The triple dots were supposed to represent truncated classes. I will edit the code so it represents less ambiguously what I meant. – Sam Feb 06 '23 at 11:50
  • @MichaelButscher overwriting the package in a way that `fillvalue` returns something different was also my first thought. However, I'd like to avoid writing my own package, if possible. – Sam Feb 06 '23 at 11:51
  • Have you tried adding a `Foo.__str__()` method? It seems to break the recursion because `f'{self.foo}` calls `Foo.__str__()` and doesn't perpetuate the recursion. – quamrana Feb 06 '23 at 12:27

1 Answers1

2

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.

GIZ
  • 4,409
  • 1
  • 24
  • 43