Circular imports
The gotcha
When module A imports B and B imports A, one of them sees a partially initialized version of the other. The error usually surfaces as ImportError: cannot import name 'X' from partially initialized module or as AttributeError later.
Minimal repro
# a.py
from b import b_func
def a_func():
return b_func() + 1
# b.py
from a import a_func
def b_func():
return a_func() + 1
>>> import a
ImportError: cannot import name 'b_func' from partially initialized module 'b'
(most likely due to a circular import)
Why it happens
When you import a:
- Python creates an empty
amodule object and starts executing top ofa.py. from b import b_functriggersimport b. Python creates emptybmodule, starts executingb.py.from a import a_funclooks upa— which exists (step 1) but is partially initialized (noa_funcyet, since step 1 hasn’t gotten past line 1).- The
from a import a_funcraises becausea_funcdoesn’t exist yet on the half-builta.
Two modules importing each other is a code-smell — it usually means the boundary is wrong.
How to fix
Option 1: refactor — extract shared code
If A and B both need helpers, put them in a separate c.py that neither imports the other:
# c.py — shared utilities
def shared(): ...
# a.py
from c import shared
# b.py
from c import shared
This is almost always the right answer.
Option 2: lazy import (function-local)
If you must keep the cycle, defer the import until the function runs — by which time both modules are fully loaded:
# a.py
def a_func():
from b import b_func # imported at call time, not module-load time
return b_func() + 1
Performance: each call re-runs the from ... import statement, which is cheap (just dict lookup) but not free.
Option 3: import b instead of from b import x
A module reference is created early; attribute lookup happens at call time:
# a.py
import b # creates a's reference to b's namespace
def a_func():
return b.b_func() + 1 # attribute resolved when called
This works as long as b.b_func exists by the time a_func runs.
Option 4: TYPE_CHECKING for type hints
If the cycle is only for type annotations:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from b import B # imported only by type checker, not at runtime
Interview angle
“You see ImportError: cannot import name X from partially initialized module Y. What’s happening and how do you fix it?” The answer that lists all four options (refactor, lazy import, module-level import, TYPE_CHECKING) and recommends refactoring first is strong.