Breaking the Loop: A Deep Dive into Circular Imports in Python

Breaking the Loop: A Deep Dive into Circular Imports in Python
Photo by Jason Sung / Unsplash

Introduction

Every seasoned Python developer must have seen at least once in their life a circular import issue. You have quickly fixed it via finding a solution from Stackoverflow or AI. However, have you really understood what caused the issue? Yes, two files importing each other is not allowed, but what goes under the hood? Let's discover together..

The Example

The best way to learn is by example. Let's say we have the following 2 files(modules) called module1.py and module2.py,
and these are their contents:

# module1.py
import module2
x = 1

# module2.py
import module1
print(module1.x)

Let's run the first module in our terminal:

$ python module1.py

What happens? Let's go through step by step:

  1. Ok, we are running module1 . Python is going to cache this, in case it will be imported to another module later on. So, it's added to sys.modules.
sys.modules is a dictionary that caches all imported modules, mapping their names to the actual module objects, so Python doesn’t reload them on subsequent imports.
  1. When the code is at import module2 step(line 2), python starts importing module2. We can also say: Python executes the top level code of module2
Top-level code is the part of a Python file that runs immediately when the file is executed or imported, outside of any function or class definitions.

So, any class or function definitions will be run but not the contents inside!, and also print statements are run in the import process.

  1. Now we are at line 6, in module2 starting to import module1.py . You would think Python would start the import a process, but no. On step 1, module1 has already been cached. Therefore, it continues.
  2. Now, we are at print(module1.x) line 7. Ok, Python knows that module1 is cached. So, let's use that. module1.x ?? There is no x under module1. Remember that x = 1 hasn't run yet!
  3. Python throws AttributeError meaning I can't find x attribute under module1.

Benefits of Caching Modules

Imagine Python wasn't caching modules. Then everytime a module is imported, top level code would run. Python saves time and processing power by caching modules.

Also at step 3, we covered that code continues. If cashing was not implemented, then infinite loop would occur. That's being avoided.

Conclusion

Now you have a better understanding of circular imports. Hopefully, you won't need to search for helpers like stackoverflow, AI but resolve with your grasp on the topic.

Also, understanding circular imports might come handy in interviews ;)