Search code examples
pythoncyclic-referencemutual-recursioncyclic-dependencyforward-reference

Mutual recursion between objects in Python


I'm currently working on a module that allows users to build an arbitrary task network model (for use in a discrete event simulation) by creating instances of task objects (my module provides a task class). Among other things, a task contains logic describing the effects of its completion, such as the initiation of a different task. In this way, an instance of the task class may refer to one or more other instances, with the possibility of cyclical references/mutual recursion.

Here is an extremely simplified version of my code:

TaskModule.py

class Task(object):
    def __init__(self, name, effect):
        self.name = name
        self.effect = effect

def execute(task):
    task.effect()

TaskTest.py

task1 = task("Do the first thing", execute(task2))
task2 = task("Do the second thing", execute(task3))
task3 = task("Do the third thing", execute(task1))

The problem with this implementation is that I refer to task2 and task3 before they have been defined. That wouldn't be the end of the world if I could rule out cyclical references- it would just be a question of rearranging the order in which the objects are instantiated- but I believe I should accommodate that possibility. I have considered a couple potential workarounds- most would involve requiring the user to reference tasks indirectly (i.e. by some unique identifier value)- but I wonder if there is a more elegant solution to this that involves a clever form of abstraction.

Ensuring that the process of instantiating tasks/generating the task network (as seen in TaskTest.py) is as simple and easy as possible is a top priority for this project, since that is what the users of my module will be spending most of their time doing.

I tried searching, but it seems like most questions on the topic of mutual recursion/cyclical references concern functions or classes rather than instances.


Solution

  • So, I think the issue here is that names and objects are being conflated. I would maybe use a structure where the task objects are organized in a dictionary and a string or an enumeration are used as keys. This way you can refer to names before they are assigned.

    class TaskManager:
        def __init__(self, tasks=None):
            self.tasks = tasks or {}
    
        def register(self, name, task):
            self.tasks[name] = task
    
        def execute(self, name):
            self.tasks[name].effect()