16

I've written a container type in Python and I'm trying to write a robust __repr__ method that correctly handles the case where the container contains itself.

For example, here's what the built-in list does:

>>> x = []
>>> x.append(x)
>>> repr(x)
'[[...]]'

Container types written in C for CPython can achieve this functionality by using Py_ReprEnter and Py_ReprLeave. Is there equivalent functionality in pure-Python, or do I need to create my own?

Daniel Stutzbach
  • 74,198
  • 17
  • 88
  • 77

2 Answers2

11

If you're using Python 3 you can make use of the reprlib.recursive_repr decorator.

hwiechers
  • 14,583
  • 8
  • 53
  • 62
7

You can create your own, but it's a bit of a pain if you want to do it properly: you shouldn't store a ‘being repr'd’ marker flag on the object itself because that's not thread-safe. Instead you can store a thread-local set of your instances that are being repr'd.

A much cheaper solution is to depend on a built-in repr that takes care of recursion, eg.:

def __init__(self, *list):
    self._list= list
def __repr__(self):
    return 'mything('+repr(self._list)[1:-1]+')')

As long as one object in a recursion loop causes Py_ReprEnter to happen, repr can't form a complete loop.

How do I create a thread-local set of instances?

With the threading module:

class MyThing(object):
    _local= threading.local()
    _local.reprs= set()

    def __repr__(self):
        reprs= MyThing._local.reprs
        sid= id(self)
        if sid in reprs:
            return 'MyThing(...)'
        try:
            reprs.add(sid)
            return 'MyThing(%r)' % self.something
        finally:
            reprs.remove(sid)
bobince
  • 528,062
  • 107
  • 651
  • 834
  • Unfortunately, I have to compute the repr string dynamically, so I can't depend on the built-in repr() in the way that you describe. How do I create a thread-local set of instances? – Daniel Stutzbach May 18 '10 at 16:36
  • @DanielStutzbach , why wrapped in try/finally block? – akaRem May 25 '13 at 10:51
  • 1
    @akaRem: `self.something` is a placeholder for some more complex work involving recursion. On a bad day something in there might cause an exception. If that happens we want to make sure the `reprs` list is cleared out on the way back out otherwise it will accumulate instances as the errors pile up, causing more and more `MyThing`s to render as `MyThing(...)` even when there is no recursion. – bobince May 26 '13 at 09:34
  • Your `self._list` is actually a tuple, which adds an undesirable comma if it only has a single element ... also note that the `[1:-1]` just cuts off the `'('` and `')'` which you then immediately add again - thus it's equivalent to just `return 'mything%r' % self._list` – o11c Jun 11 '18 at 04:32