Search code examples
pythonpython-3.xpython-importcircular-dependencyforward-reference

Construct a Python class inside its method and different file


I want to construct an object from the same class in one of its methods. However, the method is in a different file. How can I do the following?

file1.py

class MyClass():
    def __init__(self, i): 
        print(i)
    from file2 import my_fun

file2.py

def my_fun(self):
    if i == 1:
        new_obj = MyClass(i + 1)

I get the following error:

NameError: name 'MyClass' is not defined

Solution

  • file2 has to import file1.MyClass somewhere. Importing inside my_fun is of course possible, but not desirable. Doing from file1 import MyClass at the beginning of the file will cause a circular dependency problem: MyClass may not have been executed yet at that point, and so may not exist.

    Option 1:

    import file1
    
    def my_fun(self):
        if i == 1:
            new_obj = file1.MyClass(i + 1)
    

    In this case, file1 will refer to the correct module object, even if its __dict__ is not completely filled in at that point. By the time my_fun can be called, file1 will have been loaded completely.

    Option 2:

    class MyClass():
        def __init__(self, i): 
            print(i)
    
    from file2 import my_fun
    MyClass.my_fun = my_fun
    del my_fun
    
    def my_fun(self):
        if i == 1:
            new_obj = MyClass(i + 1)
    
    from file1 import MyClass
    

    In this version, you place all the import statements after definitions of things imported to another file have had a chance to run. This is not very elegant at all.

    Option 3

    def my_fun(self):
        from file1 import MyClass
        if i == 1:
            new_obj = MyClass(i + 1)
    

    This is the ugliest option of all. You remove the circular dependency by removing file2's dependency on file1. The dependency is exclusive to my_fun, and does not manifest itself until runtime, when it is totally resolvable. This is not that terrible because after the first execution, the import is basically just assigning

    MyClass = sys.modules['file1'].MyClass
    

    TL;DR

    You have a couple of ways of bypassing circular dependencies by carefully working around the execution paths of module loading. However, all options shown here are code smell, and you should avoid them. They indicate an inappropriate level of coupling between the modules that should be resolved in other ways.