Python Circular Import
- Reproduce Circular Import Error in Python
- Solve Circular Import Error in Python
- Solve Circular Import Error Caused by Type Hint in Python
In this article, we will learn how circular imports commonly arise and the main ways to fix them. We will also see how to fix a circular import error caused by a type hint in python.
Reproduce Circular Import Error in Python
Have you ever come across an error when trying to import a module? In this case, we get an error that is shown below.
from My_Module_A import My_FUNC_A
def My_main_Func():
My_FUNC_A()
if __name__ == "main":
My_main_Func()
Output:
ImportError: cannot import name 'My_FUNC_A' from partially initialized module 'My_Module_A' (most likely due to a circular import)
It tells us that the most likely cause is a circular import. Here is the simplest and most common way that this kind of import cycle can happen at runtime main tried to import something from My_Module_A
.
So, it starts initializing My_Module_A
and running the code from My_Module_A
, but the first line in My_Module_A
is to import something from module MY_Module_B
.
So, it stops initializing module My_Module_A
because MY_Module_B
has to be initialized first. And then, it hops over to MY_Module_B
and starts running that code instead.
But, the first line in module MY_Module_B
is needed something from My_Module_A
, so it stops running MY_Module_B
and goes over to My_Module_A
.
And it would like to start initializing My_Module_A
, but it realizes that My_Module_A
is already in the initialization process. So that’s where the error occurs.
We can see this chain of events unfold in the traceback. Here we see in main.py
; it tries to import function My_FUNC_A
from module My_Module_A
, which triggered the import of function MY_Func_B2
from module MY_Module_B
.
And, MY_Module_B.py
triggered the import of function My_FUNC_A
again from module My_Module_A
, where the final error occurred.
Traceback (most recent call last):
File "c:\Users\Dell\Desktop\demo\main.py", line 1, in <module>
from My_Module_A import My_FUNC_A
File "c:\Users\Dell\Desktop\demo\My_Module_A.py", line 1, in <module>
from MY_Module_B, import MY_Func_B2
File "c:\Users\Dell\Desktop\demo\MY_Module_B.py", line 1, in <module>
from My_Module_A import My_FUNC_A
ImportError: cannot import name 'My_FUNC_A' from partially initialized module 'My_Module_A' (most likely due to a circular import) (c:\Users\Dell\Desktop\demo\My_Module_A.py)
We have an import time cycle between the modules My_Module_A
and MY_Module_B
, but if you look at the names we imported, we do not use them at import time.
We use them when this function My_FUNC_A()
or when this function MY_Func_B2()
is called. Since we are not calling these functions at import time, there is no true cyclic dependency, which means we can eliminate this import cycle.
Solve Circular Import Error in Python
There are three common ways to resolve this issue; the first is to take your import and put it inside the function.
In python, imports can happen at any time, including when a function is called; if you need this function MY_Func_B1()
, then it could be the import inside the My_FUNC_A()
function.
def My_FUNC_A():
from MY_Module_B import MY_Func_B1
MY_Func_B1()
It could be even more efficient because if you never call the function My_FUNC_A()
, then it may be that you never even have to import MY_Func_B1
at all. For example, let’s say you have a bunch of different functions in My_Module_A
, and many different ones use this function MY_Func_B1()
.
In that case, the better solution would be to import the module MY_Module_B
directly and in the functions where you use it, use the longer name like MY_Module_B.MY_Func_B1()
. So go ahead and do this conversion for all modules involved in the cycle.
import MY_Module_B
def My_FUNC_A():
MY_Module_B.MY_Func_B1()
Let’s see how this resolves the import cycle at runtime error. First, our My_main_Func()
function tries to import something from My_Module_A
, causing My_Module_A
to start running. Then, in My_Module_A.py
, we get to import module MY_Module_B
, which triggers MY_Module_B
to start running.
In MY_Module_B.py
, when we get to the import module My_Module_A
, the module has already started initializing; this module object technically exists. Since it exists, it does not begin rerunning this.
It just says, okay, that exists, so the MY_Module_B
module will continue, finish its import, and then after that import is done, the My_Module_A
module will finish importing.
Output:
15
The third and final common way to eliminate this import cycle is to merge the two modules. So, again, no imports, no import cycles; however, if you had two or maybe even more modules in the first place, you probably had them for a reason.
We are not just going to recommend that you take all of your modules and merge them into one; that would be silly. So, you should probably prefer using one of the first two methods.
Source code of My_Module_A.py
file.
import MY_Module_B
def My_FUNC_A():
print(MY_Module_B.MY_Func_B1())
Source code of MY_Module_B.py
file.
import My_Module_A
def MY_Func_B1():
return 5 + 10
def MY_Func_B2():
My_Module_A.My_FUNC_A()
Source code of main.py
file.
from My_Module_A import My_FUNC_A
def My_main_Func():
My_FUNC_A()
if __name__ == "main":
My_main_Func()
Solve Circular Import Error Caused by Type Hint in Python
Let’s look at another example, the following most common kind of import cycle you will run due to using type hints. This example is unlike the previous one because type annotations are defined on a function or class definition.
When the function is defined, or the class is defined but not defined when we run it, the most common use case of type hinting is not even at runtime. If all we care about is static analysis, then we do not even need to do the import at runtime.
The typing
module provides this variable, TYPE_CHECKING
is a false constant at runtime. So, if we put all the imports we need for type checking in one of these, then none will happen at runtime.
Avoiding the import loop altogether, if you import this class Mod_A_class
, you will get a name error because this name Mod_A_class
has not been imported.
Output:
Traceback (most recent call last):
File "c:\Users\Dell\Desktop\demo\main.py", line 1, in <module>
from My_Module_A import My_FUNC_A
File "c:\Users\Dell\Desktop\demo\My_Module_A.py", line 9, in <module>
from MY_Module_B, import MY_Func_B1
File "c:\Users\Dell\Desktop\demo\MY_Module_B.py", line 22, in <module>
class Mod_b_class:
File "c:\Users\Dell\Desktop\demo\MY_Module_B.py", line 23, in Mod_b_class
def __init__(self,a : Mod_A_class):
NameError: name 'Mod_A_class' is not defined
To fix this, add this from __future__
import annotations
. What does this?
It changes the way annotations work; instead of evaluating Mod_b_class
as a name, all annotations are converted to strings.
So now you do not get an error at runtime here because even though the name Mod_b_class
is not imported, the string Mod_b_class
certainly exists. However, you should be aware that whenever you do one of these from __future__
imports, it is not necessarily guaranteed behavior forever.
Now, this causes your annotations to be handled as strings, but there is a competing proposal to make them just be evaluated lazily. Hence, they are not evaluated unless you try to look at them and inspect your type hints at runtime.
This probably will not make any difference for your code base, but you should still be aware. An alternative solution is to again use import modules
instead of from module imports
, and you will still need the annotations
from __future__
.
Complete source code of module My_Module_A.py
.
# from __future__ import annotations
# from typing import TYPE_CHECKING
# from MY_Module_B import MY_Func_B1
# if TYPE_CHECKING:
# from MY_Module_B import Mod_b_class
# def My_FUNC_A():
# b : Mod_b_class = MY_Func_B1()
# class Mod_A_class:
# def __init__(self,b : Mod_b_class):
# self.b=b
from __future__ import annotations
import MY_Module_B
def My_FUNC_A():
b: MY_Module_B.Mod_b_class = MY_Module_B.MY_Func_B1()
class Mod_A_class:
def __init__(self, b: MY_Module_B.Mod_b_class):
self.b = b
Complete source code of module MY_Module_B.py
.
from __future__ import annotations
import My_Module_A
def MY_Func_B1():
...
class Mod_b_class:
def __init__(self, a: My_Module_A.Mod_A_class):
self.a = a
Complete source code of main.py
file.
from My_Module_A import My_FUNC_A
def My_main_Func():
My_FUNC_A()
if __name__ == "main":
My_main_Func()
Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.
LinkedIn