Skip to content

Instantly share code, notes, and snippets.

@MRobertEvers
Last active May 6, 2018 19:22
Show Gist options
  • Save MRobertEvers/420b52928b32534145891f0825d7e3e2 to your computer and use it in GitHub Desktop.
Save MRobertEvers/420b52928b32534145891f0825d7e3e2 to your computer and use it in GitHub Desktop.
Circular Imports In Python 3: When are they allowed?

Circular Imports in Python 3

I recently ran into a circular import problem in Python. In my search for an answer, I often came across people simply stating that "circular imports are OK." with little to no explanation. Don't be deceived! There are cases where circular imports do and don't work! This gist will hopefully illustrate those cases.

Edit: A good Discussion about cyclic imports. Also check this Stack Overflow Thread. The answer by pythoneer (at the time of writing, the second most voted answer), is one of the misleading statements that drove me to write this gist.

When It Works

Say we have two classes, Lefty and Righty, each in their own file.

# File Righty.py
import Lefty
class Righty:
    def __init__(self):
        print("Righty")

    def get_a_lefty(self):
        return Lefty.Lefty()
        

# File Lefty.py
import Righty
class Lefty:
    def __init__(self):
        print("Lefty")

    def get_a_righty(self):
        return Righty.Righty()

and a third file to run something

# File Main.py
import Righty
myRighty = Righty.Righty()
myLefty = myRighty.get_a_lefty()

When Main.py is run, the output is Righty Lefty

Circular imports work!

When It Doesn't Work

Say we have two classes, Tank and BlueTank, each in their own file. (Beware: This is an extremely contrived example for illustration)

# File Tank.py
import BlueTank
class Tank:
    def __init__(self):
        print("Tank")
        
    def get_blue_tank(self):
        return BlueTank.BlueTank()

# File BlueTank.py
import Tank

# Notice this line
class BlueTank(Tank.Tank):
    def __init__(self):
        print("BlueTank")

and again, a third file to run it,

# File Main.py
import Tank
myTank = Tank.Tank()
myBlueTank = myTank.get_blue_tank()

When Main.py is run, the output is

Traceback (most recent call last):
  File "./Main.py", line 8, in <module>
    import Tank
  File ".\Tank.py", line 1, in <module>
    import BlueTank
  File ".\BlueTank.py", line 4, in <module>
    class BlueTank(Tank.Tank):
AttributeError: module 'Tank' has no attribute 'Tank'

Uh oh! It doesn't work! The line that catches my attention is

AttributeError: module 'Tank' has no attribute 'Tank'

That's clearly not true. Tank is a class in the Tank.py file... what's happening?

What's The Difference?

To understand the difference, take a look at this example

# File Main.py (only contains an import statement)
import MyClass

# File MyClass.py (only contains a print statement)
print("Cat")

When Main.py is run, the output is

Cat

When "import MyClass" is executed, it executes each statement in MyClass.py. When python imports a module, it executes each statement in it. Put simply, it executes each statement with no indent before it. Statements that are often like this are import <module>, class <classname>:, def <function>:, or, like in the example, function calls like print("Cat"). In order for the interpreter to execute these statements, it must know what the words in the statement are! Going back to the Tank example,

class BlueTank(Tank.Tank):

notice that the class statement uses the name Tank.Tank. Thus, in order for the interpreter to execute this statement, Tank.Tank must already be defined. We have a Tank class in Tank.py so why doesn't it know Tank.Tank? Take another look at the error trace

Traceback (most recent call last):
  File "./Main.py", line 8, in <module>
    import Tank
  File ".\Tank.py", line 1, in <module>
    import BlueTank
  File ".\BlueTank.py", line 4, in <module>
    class BlueTank(Tank.Tank):
AttributeError: module 'Tank' has no attribute 'Tank'

Notice the first error statement in the trace! It's import Tank. That means the interpreter was still in the process of importing Tank when we tried to access Tank.Tank. The interpreter reached class BlueTank(Tank.Tank): before it reached the class Tank line. To illustrate this a little bit, we can tell the interpreter to import BlueTank after the class Tank: statement.

# File Tank.py
class Tank:
    def __init__(self):
        print("Tank")
        
    def get_blue_tank(self):
        return BlueTank.BlueTank()
        
import BlueTank

It works! The output is

Tank
Tank
BlueTank

Dr. Seuss would almost be proud. So, as shown in the When It Works example, given modules A and B that import each other, circular import works when B does not have to know about anything in A (classes, functions, etc) while A is importing B. That sure is a mouthful, and probably not at all clear, but the examples above should help understanding.

But the When It Doesn't Work example illustrates something else. Notice that you can call BlueTank.BlueTank() in the Tank class before the import BlueTank statement. While in the Tank.py file, BlueTank.BlueTank() comes before import BlueTank, the interpreter reaches the import BlueTank statement before it reaches the BlueTank.BlueTank() statement. Thus, in that case, circular import works. So the bold statement above is true, but there are more cases where it works.

But gosh darn that fix is ugly. If you find yourself having to do this, you may want to rethink your design (class hierarchy, function ordering, etc.) instead of putting import statements at various locations in your files. You generally want those at the top.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment