Monday, September 16, 2013

Python - Circular Imports

This morning, when I ran the django shell, I saw this error:



Looked at the error message closely, I just maked one of the common mistakes every python newbie did: Circular Imports

So, I had to refactor my code so that "A imports B, but B does not import A". In this case:

* Module tasks imports module utils but utils does not import tasks or vice versa.
* Define the utility function (call the task) directly inside the view (my_view), not through module utils. Let's call it my_func.
* In my case, I also had to import tasks locally inside my_func, not globally in views.py to make the call works (tasks.py is place the same location with settings.py):

views.py:

...
def my_func():
      import tasks
...

def my_view(request):
...
     my_func()
...



The following note of Fredrik Lundh (http://effbot.org/zone/import-confusion.htm) should be read carefully to be able to avoid this circular import issue:

In Python, things like def, class, and import are statements too.
Modules are executed during import, and new functions and classes won’t appear in the module’s namespace until the def (or class) statement has been executed.
This has some interesting implications if you’re doing recursive imports.
Consider a module X which imports module Y and then defines a function called spam:
    # module X

    import Y

    def spam():
        print "function in module x"
If you import X from your main program, Python will load the code for X and execute it. When Python reaches the import Y statement, it loads the code for Y, and starts executing it instead.
At this time, Python has installed module objects for both X and Y in sys.modules. But X doesn’t contain anything yet; the def spam statement hasn’t been executed.
Now, if Y imports X (a recursive import), it’ll get back a reference to an empty X module object. Any attempt to access the X.spam function on the module level will fail.
    # module Y

    from X import spam # doesn't work: spam isn't defined yet!
Note that you don’t have to use from-import to get into trouble:
    # module Y

    import X

    X.spam() # doesn't work either: spam isn't defined yet!
To fix this, either refactor your program to avoid circular imports (moving stuff to a separate module often helps), or move the imports to the end of the module (in this case, if you move import Y to the end of module X, everything will work just fine).


Read Fredrik Lundh's article for more "import gotchas": http://effbot.org/zone/import-confusion.htm