A Python circular import happens when two modules import each other and one of them isn’t finished loading yet. Python reports it as an ImportError or an AttributeError, usually with a message about a partially initialized module. In this tutorial, you’ll trace what Python does when that happens, then work through fixes ranging from removing the dependency to using workarounds.
To follow along, you should be comfortable with modules and packages, classes, and basic type hints.
By the end of this tutorial, you’ll understand that:
- A Python circular import happens when two modules depend on each other and one asks for a name before the other has finished loading.
- The reliable fix is to remove the dependency so that imports flow in one direction.
ImportErrorandAttributeErrorsignal the same cycle, and which one you get depends on the form of the import statement you used.- Moving an import inside the function that needs it is the only workaround that survives any import order.
- A type-only import still needs a
TYPE_CHECKINGguard, even on Python 3.14, where annotations are evaluated lazily.
To see why the error occurs where it does, you’ll start with the import machinery itself, then build a small project that reproduces the problem.
Take the Quiz: Test your knowledge with our interactive “Python Circular Imports: Why They Happen and How to Fix Them” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python Circular Imports: Why They Happen and How to Fix ThemTest your understanding of Python circular imports, from how the import system loads modules to the fixes and workarounds that break a cycle.
In Short: A Python Circular Import Is a Loop Between Modules
A circular import happens when two or more modules import each other, directly or through a chain. The cycle fails only when one module asks for a name that the other module hasn’t defined yet, because Python is still loading that module. You’ll see this as an ImportError or an AttributeError.
The reliable fix is to restructure your code so imports flow in one direction, toward the module that could exist without the other. Guard imports used only for type hints with TYPE_CHECKING. If you can’t restructure yet, move the import inside the function that needs it.
Why Python Circular Imports Fail
To understand why circular imports happen, you first need to know how Python imports modules.
Whenever Python imports a module, it first creates an entry for that module in sys.modules. It then starts executing the file from top to bottom.
Think of sys.modules as Python’s cache of imported modules. If another import requests a module that’s already in the cache, Python reuses the existing module object instead of importing it again.
The key is that Python adds the module to sys.modules before it finishes executing the file. That means the module can exist in a partially initialized state. Some functions, classes, and variables may not exist yet because Python hasn’t reached those lines of code. If another module tries to import one of those objects before it’s defined, Python raises an ImportError or AttributeError.
A Python Circular Import Example
Suppose you’re building a Python application to manage orders for an e-commerce store. You’ll build it as a shop package with a main.py script beside it:
shop_project/
│
├── shop/
│ ├── __init__.py
│ ├── order.py
│ └── product.py
│
└── main.py
The empty __init__.py makes shop a regular package. You can run the examples without it, but type checkers need it to work out that shop.order and shop.product belong together.
The package also keeps the error message accurate. From Python 3.13, if the failing module sits in the same folder as the script you run, Python suggests renaming the file instead of reporting the circular import. Keeping the modules inside shop/ avoids that misleading hint.
Every stage of this tutorial is a complete, runnable version of this project. The examples target Python 3.14, and the tutorial points out where older versions behave differently. You can download every stage here:
Get Your Code: Click here to get the free broken project and six fixed versions you’ll use to learn how circular imports break and how to fix them.
You have an Order class that tracks the product you order and any additional items you add. Since it works with Product objects, order.py imports the Product class:
shop/order.py
from shop.product import Product
SHIPPING_FEE = 4.99
class Order:
def __init__(self, product: Product, quantity: int) -> None:
self.product = product
self.quantity = quantity
self.extras: list[Product] = []
def add_shipping(self) -> None:
self.extras.append(Product("Shipping", SHIPPING_FEE))
def get_total(self) -> float:
subtotal = self.product.price * self.quantity
return subtotal + sum(extra.price for extra in self.extras)
Now look at product.py. Its order() method creates an Order object, and is_in_order() accepts an Order object to check whether the product belongs to that order. So product.py imports the Order class from order.py:
shop/product.py
from shop.order import Order
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def order(self, quantity: int) -> Order:
return Order(self, quantity)
def is_in_order(self, order: Order) -> bool:
return order.product.name == self.name
The main.py script creates a product and places an order for it:
main.py
from shop.product import Product
laptop = Product("Laptop", 1000.0)
order = laptop.order(1)
order.add_shipping()
print(order.get_total())
Run it from the project root, and Python stops when it encounters the cycle:
$ python main.py
Traceback (most recent call last):
File "main.py", line 1, in <module>
from shop.product import Product
File "shop/product.py", line 1, in <module>
from shop.order import Order
File "shop/order.py", line 1, in <module>
from shop.product import Product
ImportError: cannot import name 'Product' from partially initialized
⮑ module 'shop.product' (most likely due to a circular import)
⮑ (shop/product.py)
Python stopped while shop.product was still loading, which is why the message calls it a partially initialized module.
Tracing the Import Sequence
To see why Python raises this ImportError, follow the imports in the order Python runs them:
main.pyrunsfrom shop.product import Product.- Python creates the
shop.productmodule, adds it tosys.modules, and starts executingproduct.py. product.pyrunsfrom shop.order import Order. Sinceorder.pyhasn’t been executed yet,shop.orderisn’t insys.modules, so Python switches to loadingorder.py.- Python creates the
shop.ordermodule, adds it tosys.modules, and starts executingorder.py. order.pyrunsfrom shop.product import Product.- Python finds
shop.productinsys.modules, but it’s still being initialized andProductisn’t available yet, so Python raisesImportError.
Step through that same sequence one import at a time:
Notice that Python adds shop.product to sys.modules in the second step, long before it defines any names in that module. The module object exists, so Python won’t start loading it a second time, but the names inside it don’t exist yet.
The problem is that product.py is waiting for order.py, while order.py is trying to import Product from product.py before product.py has finished defining it. Next, you’ll fix it.
Step-by-Step Explanation: How to Fix Circular Imports
The best way to fix circular imports is to remove the circular dependency by restructuring the code or design. In this example, an order needs a product, but a product can exist without an order. That tells you which direction the dependency should go: order.py to product.py, not the other way around. So product.py shouldn’t import Order at runtime.
Step 1: Remove the Runtime Dependency
The order() method in Product creates an Order object:
def order(self, quantity: int) -> Order:
return Order(self, quantity)
This method forces product.py to import Order. Delete the method and create the Order directly in main.py instead. This removes the runtime dependency from product.py to order.py.
Here’s shop/product.py without the order() method:
shop/product.py
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def is_in_order(self, order: Order) -> bool:
return order.product.name == self.name
is_in_order() stays in place. It needs Order only for its type hint, and Step 2 handles that case.
Note: On Python 3.13 and earlier, this version of product.py raises NameError: name 'Order' is not defined, because older versions evaluate annotations right away. Step 2 fixes this on every version.
Here’s the updated main.py:
main.py
from shop.order import Order
from shop.product import Product
laptop = Product("Laptop", 1000.0)
order = Order(laptop, 2)
order.add_shipping()
print(order.get_total())
You’ve removed the runtime behavior that required Order in product.py. But is_in_order() still names Order in its type annotation, and nothing imports it anymore.
On Python 3.14, the code runs and prints 2004.99 because Python evaluates annotations lazily. That’s only half the story. Run a type checker such as mypy on the same file, and it reports that Order isn’t defined:
$ python -m mypy shop/product.py
shop/product.py:6: error: Name "Order" is not defined [name-defined]
Found 1 error in 1 file (checked 1 source file)
Your code runs, but your type hints are broken.
So you still need the import, just not at runtime. That’s what TYPE_CHECKING is for, and you need it on every Python version, including 3.14.
Step 2: Guard the Type-Only Import
Python’s TYPE_CHECKING constant lets you import modules for static type checking without performing those imports at runtime. Basically, TYPE_CHECKING is False when Python runs the code, but static type checkers treat it as True.
Update shop/product.py like this:
shop/product.py
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from shop.order import Order
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def is_in_order(self, order: "Order") -> bool:
return order.product.name == self.name
The quotes around "Order" make the annotation a string, so Python 3.13 and earlier don’t try to look up Order at runtime. On Python 3.14, you can leave them out.
Now the runtime cycle is gone, and the program runs:
$ python main.py
2004.99
On Python 3.13 and earlier, the quotes around "Order" do real work. Without them, Python evaluates the annotation as soon as it defines is_in_order(), and you get NameError: name 'Order' is not defined. You’ll also encounter codebases that add from __future__ import annotations at the top of the file to get the same deferred behavior on older versions. You don’t need it on Python 3.14, but it’s worth recognizing when you see it.
Workarounds to Escape a Circular Dependency
Sometimes restructuring isn’t that easy. Maybe you’re working in a large codebase, or the dependency comes from a library you don’t control. In those cases, a few escape hatches can stop the circular import from failing.
To follow along, put product.py and main.py back the way they were before Step 1, with Product.order() restored and the original main.py in place. Each workaround below starts from that broken pair, so the resulting program orders one laptop instead of two and prints 1004.99.
Defer Imports to Inside Functions
Move the import statement from the top of the module into the function that needs it. Python then executes the import only when that function runs. By then, Python may have finished loading the other module, making the required class, function, or variable available.
For example, move the import inside order() in product.py:
shop/product.py
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from shop.order import Order
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def order(self, quantity: int) -> "Order":
from shop.order import Order
return Order(self, quantity)
def is_in_order(self, order: "Order") -> bool:
return order.product.name == self.name
order.py and main.py stay unchanged. The TYPE_CHECKING block at the top isn’t a runtime import, so Order still isn’t imported when product.py starts loading. Python finishes defining the Product class without switching to order.py in the middle of the process.
Later, when Product.order() is called, Python executes from shop.order import Order. This starts loading order.py, which imports Product from product.py. By this point, the Product class has already been defined, so order.py safely imports it and finishes loading.
Run it, and the order goes through:
$ python main.py
1004.99
Because product.py no longer imports shop.order while it’s loading, this version works no matter which module your program imports first. You can check this by importing shop.order first:
$ python -c "import shop.order; print('OK')"
OK
Try the same command after you move imports to the bottom or change how you import the module, and you’ll get the ImportError again.
Move Imports to the Bottom
Another way to delay an import is to move it to the bottom of the file. This lets Python define all the classes and functions before importing another module:
shop/product.py
class Product:
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def order(self, quantity: int) -> "Order":
return Order(self, quantity)
def is_in_order(self, order: "Order") -> bool:
return order.product.name == self.name
from shop.order import Order
main.py stays as it was, importing shop.product first:
main.py
from shop.product import Product
laptop = Product("Laptop", 1000.0)
order = laptop.order(1)
order.add_shipping()
print(order.get_total())
Python starts loading product.py and defines the Product class before reaching from shop.order import Order at the bottom. Python then loads order.py, which imports Product from product.py. This time, Product already exists, so the import succeeds and Python finishes loading both modules.
The program runs as expected:
$ python main.py
1004.99
However, you should avoid this approach in production code. It goes against PEP 8, which recommends placing all imports at the top of a file, and linters flag it. Flake8 reports it as E402 (“module level import not at top of file”), and Ruff reports the same code once you enable the pycodestyle rules with ruff check --select E402.
This works because main.py imports shop.product first. If something in your project imports shop.order first instead, then Python loads order.py, which imports product.py, which reaches from shop.order import Order at the bottom before order.py has defined Order. The ImportError comes back.
Change How You Import the Module
from shop.product import Product and from shop import product don’t behave the same way during a circular import.
With from shop.product import Product, Python immediately looks for Product inside shop.product. If shop.product is still being initialized and Product hasn’t been defined yet, the import fails.
With from shop import product, Python gets the product module itself. The attribute lookup happens later, when the code accesses product.Product.
Update shop/order.py as follows:
shop/order.py
from typing import TYPE_CHECKING
from shop import product
if TYPE_CHECKING:
from shop.product import Product
SHIPPING_FEE = 4.99
DEFAULT_PRODUCT = product.Product("product_a", 0.0)
class Order:
def __init__(self, product: "Product", quantity: int) -> None:
self.product = product
self.quantity = quantity
self.extras: list["Product"] = []
def add_shipping(self) -> None:
self.extras.append(product.Product("Shipping", SHIPPING_FEE))
def get_total(self) -> float:
subtotal = self.product.price * self.quantity
return subtotal + sum(extra.price for extra in self.extras)
The ImportError is gone, but this version adds DEFAULT_PRODUCT = product.Product("product_a", 0.0) at the module level in order.py. This line accesses Product immediately, raising an AttributeError:
$ python main.py
Traceback (most recent call last):
File "main.py", line 1, in <module>
from shop.product import Product
File "shop/product.py", line 1, in <module>
from shop.order import Order
File "shop/order.py", line 10, in <module>
DEFAULT_PRODUCT = product.Product("product_a", 0.0)
^^^^^^^^^^^^^^^
AttributeError: partially initialized module 'shop.product' from
⮑ 'shop/product.py' has no attribute 'Product' (most likely due to a
⮑ circular import)
Here’s what happened:
main.pystarts loadingshop.product, which immediately importsshop.order.order.pyrunsfrom shop import product. Python returns the partially initializedshop.productmodule fromsys.moduleswithout looking forProduct.order.pyrunsDEFAULT_PRODUCT = product.Product("product_a", 0.0).product.pyhasn’t reached theProductclass definition yet, so the attribute lookup fails.
To avoid the error, don’t access Product at the module level at all. Delete DEFAULT_PRODUCT and leave the only access inside add_shipping(), where it runs long after both modules have loaded.
Here’s the updated order.py:
shop/order.py
from typing import TYPE_CHECKING
from shop import product
if TYPE_CHECKING:
from shop.product import Product
SHIPPING_FEE = 4.99
class Order:
def __init__(self, product: "Product", quantity: int) -> None:
self.product = product
self.quantity = quantity
self.extras: list["Product"] = []
def add_shipping(self) -> None:
self.extras.append(product.Product("Shipping", SHIPPING_FEE))
def get_total(self) -> float:
subtotal = self.product.price * self.quantity
return subtotal + sum(extra.price for extra in self.extras)
The cycle is still there, but nothing touches product.Product too early, so both modules finish loading:
$ python main.py
1004.99
As with the previous workaround, this depends on main.py importing shop.product first. Change which module your program imports first, and the cycle fails again.
Conclusion
Circular imports are often a sign that two modules depend on each other in a way that makes them difficult to load and maintain. The best fix is to look at that dependency first. If one module can exist without the other, restructure the code so the dependency flows in one direction and remove the circular imports. If one module needs the other only for type hints, move that import under TYPE_CHECKING so Python never executes it at runtime.
Sometimes, though, you can’t restructure the code right away, so you’ve also explored a few workarounds. Moving an import inside the function that needs it is a standard technique you can keep. The other two depend on which module your program imports first, so treat them as stopgaps.
Note: Python 3.15 adds an explicit lazy import form in PEP 810. It changes when an import is resolved, which can help when the circular reference isn’t touched while the module is initializing. It doesn’t change your dependency graph, though, so a true cycle still needs restructuring. To see how it behaves, read Python Lazy Imports: Speed Up Startup With the lazy Keyword.
If you want to keep going, learn advanced import techniques to design and maintain large Python projects. For an overview, check out Python Modules and Packages: An Introduction.
Get Your Code: Click here to get the free broken project and six fixed versions you’ll use to learn how circular imports break and how to fix them.
Frequently Asked Questions
Now that you have some experience with circular imports in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.
These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.
Two or more modules that import each other, directly or through a chain, form a cycle. The error appears when one of them asks for a name the other hasn’t defined yet because Python is still partway through executing that module.
The reliable fix is to remove the circular dependency so that imports flow in one direction, usually by deleting or moving the code that points the wrong way. When restructuring isn’t an option, moving the import inside the function that needs it is the sturdiest workaround.
The exception type depends on the form of the import rather than the cycle itself. Writing from shop.product import Product fails with ImportError because Python looks for the name immediately. With from shop import product, Python defers the lookup, so you get AttributeError only if code accesses product.Product before shop.product has finished loading.
It works, but it goes against PEP 8, and linters report it as E402. It also relies on your project importing the two modules in one particular order, so it breaks as soon as something imports them in the opposite order.
It removes the runtime import, so the cycle no longer breaks your program, and type checkers can still resolve the annotation. The two modules still depend on each other in the code, so it’s a targeted fix rather than a restructuring.
Take the Quiz: Test your knowledge with our interactive “Python Circular Imports: Why They Happen and How to Fix Them” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python Circular Imports: Why They Happen and How to Fix ThemTest your understanding of Python circular imports, from how the import system loads modules to the fixes and workarounds that break a cycle.