Recursive Python objects

May 2, 2019

Recently for work I had to check that self-referential Python objects could be string-ified without endless recursion. In the process of testing my work, I had to come come up with a way of making self-referential built-in types (eg dict, list, set, and tuple).

Making a self-refential list is the easiest task because list is just a dumb mutable container. Make a list and append a reference to itself:

ls = []
ls.append(ls)
>>> ls
[[...]]
>>>

dict is similarly easy:

d = {}
d['key'] = d
>>> d
{'key': {...}}
>>>

Making a self-referential tuple is a little bit tricker because tuples cannot be modified after they are constructed (unless you use the C-API, in which case this is much easier — but that’s cheating). In order to close the loop, we’re going to have to use a little bit of indirection.

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

  def __repr__(self):
    return self.val.__repr__()

>>> C()
((...),)
>>>

Here we create an class that stores a pointer to itself in a tuple. That way the tuple contains a pointer to an object that contains the tuple — A->B->A.

The solution is nearly the same for set:

class C:
  def __init__(self):
    self.val = set((self,))

  def __repr__(self):
    return self.val.__repr__()

>>> C()
{set(...)}
>>>

Note that simpler solutions like directly adding to the set (below) don’t work because sets are not hashable, and hashable containers like tuple depend on the hashes of their contents.

s = set()
s.add(s)  # nope
s.add((s,))  # still nope

There’s not a whole lot of point in doing this, but it was a fun exercise.