Skip to content Skip to sidebar Skip to footer

Create Circular References Between Class Instances?

I'm trying to construct a class that allows an instance to point to another class, but I want these to eventually form a loop (so instance A → Instance B → Instance C → Insta

Solution 1:

You need to create the objects first, then link them.

item_a = CyclicClass("Item A", None)
item_b = CyclicClass("Item B", item_a)
item_a.next_item = item_b

Think of the second argument to CyclicClass as a convenience, rather than the primary way of linking two objects. You can emphasize that by making None the default parameter value.

classCyclicClass:
    def__init__(self, name, next_item=None):
        self.name = name
        self.next_item = next_item

    defprint_next(self):
        print(self.next_item)

item_a = CyclicClass("Item A")
# item_b = CyclicClass("Item B")# item_b.next_item = item_a
item_b = CyclicClass("Item B", item_a)
item_a.next_item = item_b

Solution 2:

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

classCyclicClass:
    def__init__(self, name, next_item=None):
        self.name = name
        self.next_item = next_item

    defprint_next(self):
        print(self.next_item)


    @propertydefnext_item(self):
        ifisinstance(self._next_item, FunctionType):
            self._next_item = self._next_item()
        return self._next_item
    @next_item.setterdefnext_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 = {}

classCyclicClass:
    ...

    @propertydefnext_item(self):
        ifisinstance(self._next_item, str):
            self._next_item = cyclic_names[self._next_item]
        return self._next_item
    @next_item.setterdefnext_item(self, value):
        self._next_item = value

    @propertydefname(self):
        return self._name

    @name.setter(self)defname(self, value):
        ifhasattr(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)

Post a Comment for "Create Circular References Between Class Instances?"