# How to Fix Python Import Circular Dependency

Circular import errors occur when two or more modules depend on each other, creating an import loop that Python cannot resolve.

Error Patterns

Import Error

```text ImportError: cannot import name 'SomeClass' from partially initialized module 'module_a' (most likely due to a circular import)

ImportError: cannot import name 'User' from 'models' (most likely due to a circular import) ```

AttributeError During Import

text
AttributeError: partially initialized module 'module_a' has no attribute 'SomeClass'
(most likely due to a circular import)

Example Circular Import

```python # module_a.py from module_b import ClassB

class ClassA: def __init__(self): self.b = ClassB()

# module_b.py from module_a import ClassA

class ClassB: def __init__(self): self.a = ClassA() # Circular! ```

Diagnosis Steps

Step 1: Identify the Cycle

```bash # Use import time profiling python -X importtime your_script.py 2>&1 | grep "import"

# Or use import-linter pip install import-linter lint-imports ```

Step 2: Visualize Dependencies

python
# Add debug prints to see import order
print("Importing module_a")
from module_b import ClassB
print("module_b imported")

Step 3: Check Module Loading State

```python import sys

# Check if module is being loaded if 'module_a' in sys.modules: print("module_a already loaded") else: print("module_a not yet loaded") ```

Solutions

Solution 1: Move Import Inside Function (Deferred Import)

```python # module_a.py # DON'T: Import at module level # from module_b import ClassB

class ClassA: def __init__(self): # DO: Import inside method from module_b import ClassB self.b = ClassB() ```

Solution 2: Restructure Modules

Move shared code to a third module:

```python # types.py (new module for shared types) class SharedType: pass

# module_a.py from types import SharedType

class ClassA: pass

# module_b.py from types import SharedType from module_a import ClassA

class ClassB: pass ```

Solution 3: Use TYPE_CHECKING for Type Hints

```python # module_a.py from typing import TYPE_CHECKING

if TYPE_CHECKING: from module_b import ClassB

class ClassA: def __init__(self, b: 'ClassB') -> None: self.b = b

def get_b(self) -> 'ClassB': return self.b ```

Solution 4: Dependency Injection

```python # module_a.py class ClassA: def __init__(self, b_factory=None): if b_factory is None: from module_b import ClassB b_factory = ClassB self.b = b_factory()

# module_b.py from module_a import ClassA

class ClassB: pass

# main.py from module_a import ClassA from module_b import ClassB

a = ClassA(b_factory=ClassB) ```

Solution 5: Import Only When Needed

```python # config.py - No circular dependency class Config: pass

# database.py from config import Config

class Database: def __init__(self, config: Config): self.config = config

# app.py - Import order matters from config import Config from database import Database

config = Config() db = Database(config) ```

Solution 6: Use __all__ and Late Imports

```python # module_a.py __all__ = ['ClassA']

class ClassA: def method(self): from module_b import ClassB return ClassB()

# module_b.py __all__ = ['ClassB']

class ClassB: def method(self): from module_a import ClassA return ClassA() ```

Solution 7: Interface Pattern

```python # interfaces.py from abc import ABC, abstractmethod

class IClassA(ABC): @abstractmethod def method_a(self): ...

class IClassB(ABC): @abstractmethod def method_b(self): ...

# module_a.py from interfaces import IClassA, IClassB

class ClassA(IClassA): def __init__(self, b: IClassB): self.b = b

def method_a(self): return self.b.method_b()

# module_b.py from interfaces import IClassB

class ClassB(IClassB): def method_b(self): return "result" ```

Solution 8: Factory Pattern

```python # factories.py class Factory: _class_a = None _class_b = None

@classmethod def get_class_a(cls): if cls._class_a is None: from module_a import ClassA cls._class_a = ClassA return cls._class_a

@classmethod def get_class_b(cls): if cls._class_b is None: from module_b import ClassB cls._class_b = ClassB return cls._class_b

# module_a.py class ClassA: def __init__(self): from factories import Factory self.b = Factory.get_class_b()() ```

Common Patterns and Fixes

Django Model Circular Import

```python # DON'T: Import model at module level # from app.models import User

def get_user(user_id): # DO: Import inside function from app.models import User return User.objects.get(id=user_id) ```

Flask Application Factory

```python # extensions.py from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()

# models.py from extensions import db

class User(db.Model): pass

# app.py from flask import Flask from extensions import db from models import User

def create_app(): app = Flask(__name__) db.init_app(app) return app ```

Pydantic Model Circular Reference

```python from __future__ import annotations from typing import Optional from pydantic import BaseModel

class User(BaseModel): name: str friend: Optional[User] = None # Self-reference

# Or use model_rebuild class User(BaseModel): name: str friend: Optional['User'] = None

User.model_rebuild() # Resolve forward references ```

Prevention Tips

  1. 1.Organize code by feature, not by type
  2. 2.Use dependency injection for cross-module dependencies
  3. 3.Keep imports at the top when possible, use deferred imports sparingly
  4. 4.Use TYPE_CHECKING for type hints that cause cycles
  5. 5.Create interface modules for shared types

Project Structure to Avoid Cycles

bash
project/
├── core/
│   ├── __init__.py
│   └── interfaces.py    # Shared interfaces/types
├── module_a/
│   ├── __init__.py
│   └── service.py       # Depends on core only
├── module_b/
│   ├── __init__.py
│   └── service.py       # Depends on core only
└── main.py              # Wires everything together

Tools for Detection

```bash # Install import-linter pip install import-linter

# Create .importlinter config cat > .importlinter << EOF [importlinter] root_packages = mypackage

[importlinter:contract:modules-should-not-import-from-outside] type = layers layers = mypackage.core | mypackage.services | mypackage.api EOF

# Run check lint-imports ```

  • ModuleNotFoundError - Module cannot be found
  • ImportError - Module found but import failed
  • AttributeError - Module found but attribute missing