Reading the comments on chepner's answer, it looks like you want a lazy approach to be able to do the binding. Please note that allowing late assignment of "next_item" as in the other answer is still the "right thing to do".
That can easily be done, but them, you'd still depend on the hardcoded other-instance name. Some ORM frameworks for example, since they allow one to define inter-relationships between classes, allow you to insert other classes as strings rather than actual class objects.
But strings will work nice for objects defined on a module top-level, since they can be fetched as a global variable - but won't work if you are using your cyclic class inside a function or method. A callable that will return the non-local variable with the instance name could work:
from types import FunctionType
class CyclicClass:
def __init__(self, name, next_item=None):
self.name = name
self.next_item = next_item
def print_next(self):
print(self.next_item)
@property
def next_item(self):
if isinstance(self._next_item, FunctionType):
self._next_item = self._next_item()
return self._next_item
@next_item.setter
def next_item(self, value):
self._next_item = value
And testing on the interactive interpreter:
In [23]: def test():
...: inst1 = CyclicClass("inst1", lambda: inst2)
...: inst2 = CyclicClass("inst2", inst1)
...: return inst1, inst2
...:
In [24]: i1, i2 = test()
In [25]: i1.next_item.name
Out[25]: 'inst2'
But that approach is rather naive - and won't work if you re putting yur isntances into a list or other data-structures, unless you have a good timing triggering the attribute rendering into a real reference - at which point it is just better to allow late assignment to next_item anyway.
Not that if the "name" attributes are meant to be unique, you could modify the code above to have a global-registry of all your instances, and pass a string to identify your instances. That might suit your needs - but still will add more complications than allowing a late setting of next_item
cyclic_names = {}
class CyclicClass:
...
@property
def next_item(self):
if isinstance(self._next_item, str):
self._next_item = cyclic_names[self._next_item]
return self._next_item
@next_item.setter
def next_item(self, value):
self._next_item = value
@property
def name(self):
return self._name
@name.setter(self)
def name(self, value):
if hasattr(self, "_name"):
del cyclic_names[value]
if value in cyclic_names:
raise ValueError("An object with this name already exists")
cyclic_names[value] = self
def __del__(self):
del cyclic_names[self._name]
As you can see, the complexity for doing this work properly escalates quickly, and it may be a source of defects in your project - but still can be done if one thinks up of all nuances. (I'd use weakrefs on the global object index, for example)