Fix Python Circular Import Error: What It Is and How to Resolve It
Python circular imports cause ImportError or partially-initialized module errors. Here's why they happen and the 4 reliable ways to fix them without restructuring your entire project.
What Is a Circular Import?
Circular import happens when module A imports module B, and module B imports module A. Python starts loading A, sees it needs B, starts loading B, sees it needs A, but A is not finished loading yet. Python gives B a partially initialized version of A, causing either ImportError or AttributeError when B tries to use something from A that has not been defined yet.
Error:
ImportError: cannot import name 'User' from partially initialized module 'models' (most likely due to a circular import)
Or:
Error:
ImportError: cannot import name 'process_order' from 'services' (circular import)
The stack trace usually shows two files importing each other. That is the circular pair.
How to Diagnose It
The error message often does not name the circular pair directly. Use this to find it:
Or add a print at the top of suspected files:
When you see the same module name print twice before the error, that is the circular pair.
For Django projects:
Fix 1: Move the Import Inside the Function
Simplest fix. Instead of importing at module level, import inside the function that actually needs it. The import runs only when the function is called. By then, both modules are fully loaded.
Move the import inside the method:
Note: Local imports are slightly slower because Python checks
sys.moduleson each call, but the overhead is negligible for normal application code. In hot paths called thousands of times per second, cache the import result.
Fix 2: Use TYPE_CHECKING for Type Hints
If the circular import exists only for type hints, use the TYPE_CHECKING guard. The import runs only during static analysis, never at runtime.
Guard it with TYPE_CHECKING:
from __future__ import annotations makes all type annotations lazy strings at runtime. Python does not evaluate them at import time, which eliminates most circular imports caused purely by type hints.
Fix 3: Extract Shared Code to a Third Module
When A and B both need the same thing, and importing from each other creates the circle, extract the shared code into a new module C that neither A nor B imports back.
This is the correct architectural fix when local imports feel like a workaround. Common extraction targets are constants, base classes, type definitions, and utility functions.
Fix 4: Restructure Using Dependency Injection
Instead of modules importing each other, pass dependencies as function arguments or constructor parameters.
Pass the notification function as a parameter instead:
When Circular Imports Are a Design Signal
Circular imports often mean two modules are too tightly coupled. The four fixes above resolve the import error, but if you find yourself adding multiple local imports or the circle involves 3 or more files, it is worth restructuring.
| Pattern | Better approach |
|---|---|
| Model imports service | Model emits signal or event, service listens |
| Service imports model imports service | Extract to shared types.py |
| Utils imports app code | Utils should have no app dependencies |
__init__.py re-exports cause circles | Flatten imports, avoid __init__.py re-exports |
For complex multi-file circular imports, paste the import chain into DebugAI. It traces which module imports which across files and identifies the minimal cut point, which import to move or which file to extract.
Debug faster starting today.
Free VS Code extension. 10 sessions/day. No credit card.