Fix: ImportError: cannot import name 'X' from partially initialized module (circular import)
The Error
You try to run a Python script or start your application and get this:
Python 3.10+:
Traceback (most recent call last):
File "app.py", line 1, in <module>
from models import User
ImportError: cannot import name 'User' from partially initialized module 'models' (most likely due to a circular import) (/path/to/models.py)Older Python 3:
Traceback (most recent call last):
File "app.py", line 1, in <module>
from models import User
ImportError: cannot import name 'User' from 'models' (/path/to/models.py)Another common variation:
Traceback (most recent call last):
File "app.py", line 1, in <module>
from utils import helper
ImportError: cannot import name 'helper' from partially initialized module 'utils' (most likely due to a circular import) (/path/to/utils.py)The key phrase is “partially initialized module” and “most likely due to a circular import.” Python is telling you that while it was in the middle of loading one module, that module tried to import something from another module that in turn tried to import from the first one. The first module isn’t fully loaded yet, so the name you’re trying to import doesn’t exist yet.
Why This Happens
A circular import occurs when two or more modules depend on each other, directly or indirectly. Python’s import system works by executing module files top to bottom. When module A tries to import from module B, Python starts executing module B. But if module B also tries to import from module A, Python doesn’t start executing module A again (that would cause infinite recursion). Instead, it returns whatever has been defined in module A so far — which is a partially initialized module. If the name you need hasn’t been defined yet at that point, you get the ImportError.
Here is a minimal example:
# module_a.py
from module_b import function_b
def function_a():
return "A"# module_b.py
from module_a import function_a
def function_b():
return function_a()When you run module_a.py:
- Python starts loading
module_a.py. - The first line is
from module_b import function_b, so Python pausesmodule_aand starts loadingmodule_b.py. - The first line of
module_b.pyisfrom module_a import function_a. Python sees thatmodule_ais already being loaded, so it tries to grabfunction_afrom whatevermodule_ahas defined so far. - But
function_ahasn’t been defined yet (Python only got through line 1 ofmodule_a.pybefore pausing). So you getImportError: cannot import name 'function_a'.
This is not a bug in Python. It is an intentional design choice to prevent infinite import loops. The error is telling you that your module dependency graph has a cycle, and you need to break it.
Circular imports are especially common in larger projects where models reference each other, utility functions depend on application logic, or framework code (like Django models and serializers) naturally creates bidirectional dependencies.
If you are also seeing ModuleNotFoundError alongside this issue, that is a different problem. See Fix: ModuleNotFoundError: No module named in Python for that.
Fix 1: Move the Import Inside the Function (Lazy Import)
The simplest and most common fix. Instead of importing at the top of the file, import inside the function that actually needs it. This delays the import until the function is called, by which time both modules are fully loaded.
Before (broken):
# module_a.py
from module_b import function_b
def function_a():
return function_b()After (fixed):
# module_a.py
def function_a():
from module_b import function_b
return function_b()The import only runs when function_a() is called. By that time, module_b is fully initialized and function_b exists.
This is the go-to fix when you have one or two functions that cause the cycle. It is a common pattern in large Python codebases and is not considered an anti-pattern. Python caches modules after the first import, so the performance cost of importing inside a function is negligible after the first call.
However, if you find yourself adding lazy imports everywhere, it is a sign that your modules are too tightly coupled and you should consider restructuring.
Fix 2: Restructure Your Modules
The cleanest long-term solution is to reorganize your code so that the circular dependency no longer exists. Usually this means extracting the shared logic into a third module that both original modules can import from without depending on each other.
Before (circular):
# users.py
from orders import get_user_orders
def get_user(user_id):
...
def get_user_with_orders(user_id):
user = get_user(user_id)
user.orders = get_user_orders(user_id)
return user# orders.py
from users import get_user
def get_order(order_id):
...
def get_user_orders(user_id):
user = get_user(user_id)
...After (restructured):
# users.py
def get_user(user_id):
...# orders.py
def get_order(order_id):
...
def get_user_orders(user_id):
from users import get_user
user = get_user(user_id)
...# user_service.py (new module -- combines both)
from users import get_user
from orders import get_user_orders
def get_user_with_orders(user_id):
user = get_user(user_id)
user.orders = get_user_orders(user_id)
return userThe key idea is that the dependency flows in one direction. user_service depends on both users and orders, but neither users nor orders depends on user_service. This eliminates the cycle.
Fix 3: Use import module Instead of from module import name
When you use import module (instead of from module import name), Python only needs the module object to exist. It does not need the specific name to be defined at import time. The name is looked up later, when you actually use it.
Before (broken):
# module_a.py
from module_b import function_b
def function_a():
return function_b()After (fixed):
# module_a.py
import module_b
def function_a():
return module_b.function_b()This works because import module_b just needs the module_b module object to exist in sys.modules. It does not need function_b to be defined yet. When function_a() is called later, module_b is fully loaded, and module_b.function_b resolves correctly.
This fix does not work in every scenario. If both modules use from X import Y at the top level, you need to change at least one of them. But it is a quick fix that requires minimal code changes.
Fix 4: Create a Shared/Common Module
When two modules depend on each other because they share types, constants, or utility functions, extract those shared pieces into a separate module.
Before (circular):
# auth.py
from user import User, DEFAULT_ROLE
class AuthService:
def login(self, username, password):
user = User(username)
user.role = DEFAULT_ROLE
...# user.py
from auth import AuthService
DEFAULT_ROLE = "viewer"
class User:
def __init__(self, username):
self.username = username
def authenticate(self):
service = AuthService()
...After (shared module):
# constants.py
DEFAULT_ROLE = "viewer"# user.py
from constants import DEFAULT_ROLE
class User:
def __init__(self, username):
self.username = username
self.role = DEFAULT_ROLE
def authenticate(self):
from auth import AuthService # lazy import for remaining dependency
service = AuthService()
...# auth.py
from user import User
from constants import DEFAULT_ROLE
class AuthService:
def login(self, username, password):
user = User(username)
user.role = DEFAULT_ROLE
...By pulling DEFAULT_ROLE into constants.py, you remove one of the reasons auth.py needed to import from user.py. The remaining dependency (User.authenticate needing AuthService) is handled with a lazy import.
Fix 5: Use TYPE_CHECKING for Type Hints
If the circular import only exists because of type annotations, Python provides an elegant solution. The typing.TYPE_CHECKING constant is True only when a type checker (like mypy or pyright) is analyzing the code. At runtime, it is False, so the import never actually executes.
# module_a.py
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from module_b import ClassB
class ClassA:
def process(self, obj: ClassB) -> None:
...# module_b.py
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from module_a import ClassA
class ClassB:
def handle(self, obj: ClassA) -> None:
...The from __future__ import annotations line is important. It makes all annotations lazy (they are stored as strings and not evaluated at runtime), which means Python won’t try to resolve ClassB or ClassA when the module loads. Without this line, Python would still try to evaluate the type hint at class definition time and fail.
This pattern is widely used in modern Python codebases and is the recommended approach when circular imports are caused solely by type annotations.
Fix 6: Django Circular Imports (Models, Signals, Serializers)
Django projects frequently run into circular imports because models, serializers, views, and signals naturally reference each other. Django provides built-in tools to handle this.
Models referencing each other
Use a string reference instead of importing the model directly. Django resolves the string to the actual model class at runtime:
# orders/models.py
from django.db import models
class Order(models.Model):
# Instead of: from users.models import User
# Use a string reference:
user = models.ForeignKey("users.User", on_delete=models.CASCADE)The string format is "app_label.ModelName". Django handles the import internally, so you never create a circular dependency.
Signals
Signal handlers often import models from multiple apps. Register them in apps.py using the ready() method:
# orders/apps.py
from django.apps import AppConfig
class OrdersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "orders"
def ready(self):
import orders.signals # noqa: F401# orders/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from orders.models import Order
@receiver(post_save, sender=Order)
def order_created(sender, instance, created, **kwargs):
if created:
# safe to import here -- all apps are loaded
from notifications.models import Notification
Notification.objects.create(order=instance)By the time ready() runs, all Django apps and models are fully loaded, so imports inside signal handlers won’t cause circular import issues.
If you are encountering database-related errors in Django rather than import errors, see Fix: OperationalError: no such table in Django.
Serializers
Django REST Framework serializers often need to reference each other. Use lazy imports or the get_serializer pattern:
# users/serializers.py
from rest_framework import serializers
from users.models import User
class UserSerializer(serializers.ModelSerializer):
orders = serializers.SerializerMethodField()
class Meta:
model = User
fields = ["id", "username", "orders"]
def get_orders(self, obj):
from orders.serializers import OrderSerializer
return OrderSerializer(obj.order_set.all(), many=True).dataThe lazy import inside get_orders avoids the circular dependency because the import happens at serialization time, not at module load time.
Fix 7: Flask Circular Imports (Application Factory Pattern)
Flask applications commonly hit circular imports when the app object is defined in one module and routes or models are defined in others. The application factory pattern is the standard solution.
Before (broken):
# app.py
from flask import Flask
from routes import bp # circular: routes.py imports app
app = Flask(__name__)
app.register_blueprint(bp)# routes.py
from app import app # circular: app.py imports routes
@app.route("/")
def index():
return "Hello"After (application factory):
# app.py
from flask import Flask
def create_app():
app = Flask(__name__)
from routes import bp
app.register_blueprint(bp)
return app# routes.py
from flask import Blueprint
bp = Blueprint("main", __name__)
@bp.route("/")
def index():
return "Hello"# wsgi.py
from app import create_app
app = create_app()The factory pattern eliminates the circular import because routes.py no longer imports the app object. It defines a Blueprint instead. The create_app() function imports the blueprint inside the function body, after the app is created. This is the pattern recommended by the Flask documentation.
For Flask extensions that need the app object (like Flask-SQLAlchemy), use init_app():
# extensions.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()# app.py
from flask import Flask
from extensions import db
def create_app():
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
db.init_app(app)
from routes import bp
app.register_blueprint(bp)
return appFix 8: __init__.py Barrel Import Issues
Large Python packages often use __init__.py to re-export names for convenience. This creates a barrel module pattern that is prone to circular imports.
Problematic structure:
# mypackage/__init__.py
from mypackage.models import User, Order
from mypackage.services import UserService, OrderService# mypackage/services.py
from mypackage.models import User # This actually imports from __init__.py first!
class UserService:
...When Python imports mypackage.services, it first needs to load mypackage/__init__.py. That __init__.py tries to import from mypackage.services, which isn’t loaded yet. Circular.
Fix option 1: Remove barrel imports and import directly
# Instead of:
from mypackage import User
# Use:
from mypackage.models import UserFix option 2: Use lazy imports in __init__.py
# mypackage/__init__.py
def __getattr__(name):
if name == "User":
from mypackage.models import User
return User
if name == "OrderService":
from mypackage.services import OrderService
return OrderService
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")The __getattr__ approach (Python 3.7+) delays the import until the name is actually accessed. This is how several major Python libraries handle their public APIs without running into circular imports.
Fix option 3: Control import order in __init__.py
# mypackage/__init__.py
# Import in dependency order -- models first, then services that depend on models
from mypackage.models import User, Order
from mypackage.services import UserService, OrderServiceMake sure the modules that are depended upon are imported first. And critically, have services.py import directly from mypackage.models (not from mypackage), so it doesn’t trigger the __init__.py again.
Fix 9: Dependency Injection Pattern
Instead of importing a dependency directly, pass it in as a parameter. This completely eliminates the import dependency between the two modules.
Before (circular):
# validator.py
from database import save_record
class Validator:
def validate_and_save(self, data):
if self.is_valid(data):
save_record(data)# database.py
from validator import Validator
def save_record(data):
v = Validator()
v.validate(data)
...After (dependency injection):
# validator.py
class Validator:
def validate_and_save(self, data, save_fn):
if self.is_valid(data):
save_fn(data)# database.py
from validator import Validator
def save_record(data):
v = Validator()
v.validate(data)
...
def process(data):
v = Validator()
v.validate_and_save(data, save_fn=save_record)Now validator.py doesn’t import anything from database.py. The save_fn is passed in at call time. This pattern makes the code more testable too, since you can easily pass a mock function in tests.
For class-based dependency injection:
class Validator:
def __init__(self, save_fn=None):
self.save_fn = save_fn
def validate_and_save(self, data):
if self.is_valid(data):
self.save_fn(data)This approach scales well and is especially useful in larger applications where you want to decouple modules without worrying about import order.
Still Not Working?
Indirect circular imports
The cycle might not be between just two modules. Module A imports B, B imports C, and C imports A. These indirect cycles are harder to spot. Use a tool to visualize your import graph:
pip install pydeps
pydeps mypackage --clusterOr trace the imports manually by reading the traceback carefully. Python’s traceback shows the full chain of imports that led to the error.
Import side effects
If a module runs code at import time (like calling functions, creating database connections, or initializing global state), that code might trigger imports that create cycles. Move side effects into functions that are called explicitly, not at import time.
# Bad: runs at import time
db = connect_to_database() # this might import config, which imports this module
# Good: runs when you call it
def get_db():
if not hasattr(get_db, "_connection"):
get_db._connection = connect_to_database()
return get_db._connectionCircular imports that only fail sometimes
If the error only appears when you run a specific script but not others, it depends on which module Python loads first. The order matters because it determines which module is “partially initialized” when the cycle is hit. This doesn’t mean the circular import is harmless in other cases — it’s still a latent bug that can surface when import order changes.
Checking your Python and pip setup
If you’ve fixed the circular import but are still seeing import errors, the problem might be elsewhere. Make sure your packages are installed correctly and that pip can build the packages you need. On newer systems, you may also need to deal with externally managed Python environments.
Debugging the import
Add print statements to see what’s happening during import:
# module_a.py
print(f"module_a: starting import, __name__={__name__}")
from module_b import something
print(f"module_a: finished import")You can also use Python’s verbose import flag to see every import step:
python -v app.pyThis prints a line for every module Python imports, showing you the exact order and where each module is loaded from.
Make sure your code does not have syntax issues like unexpected indentation that could mask the real circular import error with a confusing traceback.
Related: If Python can’t find the module at all (rather than finding it partially initialized), see Fix: ModuleNotFoundError: No module named in Python.
Related Articles
Fix: django.db.utils.OperationalError: no such table (SQLite / PostgreSQL / MySQL)
How to fix Django's 'OperationalError: no such table' error caused by missing migrations, wrong database configuration, corrupted files, or Docker volume issues.
Fix: Python IndentationError – Unexpected Indent or Expected an Indented Block
How to fix Python IndentationError including unexpected indent, expected an indented block, and unindent does not match, caused by mixed tabs and spaces or incorrect nesting.
Fix: pip No Matching Distribution Found for <package>
How to fix the pip error 'No matching distribution found' when installing Python packages, including version conflicts, platform issues, and index problems.
Fix: PostgreSQL ERROR: duplicate key value violates unique constraint
How to fix 'duplicate key value violates unique constraint' in PostgreSQL by resetting sequences, using upserts, fixing bulk imports, and handling concurrent inserts.