Recent chats
ASKGEEZ Q&A GUIDETechnology & devices

How can I organize a small program into modules without circular imports?

Quick answer

Map which modules import one another and look for a path that leads back to its starting point. If two modules need the same small function or constant, consider moving it into a third, lower-level module that neither depends on the other. Otherwise, remove unnecessary imports or narrow them to what is needed. Python initializes modules during import, so mutual imports during initialization can encounter a module that is not fully initialized. Check the revised import flow in your program; these patterns are not guaranteed fixes. [S1]

LET’S WORK THROUGH IT

The question, the details, the next step.

Question

I have a small Python program with orders.py and reports.py. Each imports something from the other, and I sometimes get an error about a name not being available. How should I start untangling it?

Geez

Start by writing down which module imports which, including imports that happen during module initialization. For example, orders imports reports, while reports imports orders: that makes a loop. Python initializes a module when it is imported; if modules import each other during initialization, one may be encountered before its initialization is complete. [S1]

Then identify what each module actually needs from the other. A report may need order data, while order-processing code needs only a formatting helper. Those are different dependencies and may not need to point both ways. Avoid rearranging imports at random before identifying what is genuinely shared.

Follow-up question

Both files use a small function that turns an order into a display label. Would a shared module help? What might that layout look like?

Geez

If the function genuinely belongs to neither module, move it into a small, lower-level module. Both modules can depend on that shared module instead of importing each other for the function. This is a design response to the initialization problem, not a guaranteed fix; check the actual dependency graph and import behavior. [S1]

For illustration, assume the function needs only an order's number and status attributes, and that all three files are in the same package:

shop/
    order_labels.py
    orders.py
    reports.py
# order_labels.py
def display_label(order):
    return f"{order.number} ({order.status})"

# orders.py
from .order_labels import display_label

# reports.py
from .order_labels import display_label

The intended dependency direction is from orders and reports to order_labels, not back from order_labels to either of them. If the first two modules need other behavior from one another, inspect that separately rather than moving unrelated code into the shared module.

Follow-up question

What if the shared function is only used in one place, or I need just one name from the other module? Should I still add a third file?

Geez

Not necessarily. Keep a function with its natural owner if there is no real shared responsibility. First check whether the importing module needs anything from the other module at all; remove an unnecessary import if possible. If it needs only a specific name, narrow the import to that name or module rather than pulling in a broader set of dependencies. Narrowing can make the relationship clearer, but does not guarantee that a cycle is gone. [S1]

For example, if reports.py only needs display_label, make that need explicit in its import. But if orders.py still imports reports.py during initialization and reports.py imports orders.py, the mutual dependency still needs attention.

Follow-up question

How do I check that the new layout actually solved the cycle, especially if the package has an __init__.py?

Geez

Trace imports from the program's entry point: follow each module's imports and look for a route back to a module already on the path. Also inspect package initialization. Importing a regular Python package executes its __init__.py, which can add imports to the startup path. [S1]

Try the normal way your program starts and check the relevant imports in the environment where you run it. Confirm that the shared module does not import the modules it is meant to support. The import system's behavior depends on the actual dependency graph and initialization; no particular refactor is guaranteed to work for every program or setup. [S1]

A practical checklist

  • List each module's imports, including imports in package __init__.py files.

  • Trace import paths and mark any route that returns to its starting module.

  • Remove imports that are not needed; narrow imports to the required name or module.

  • Move only genuinely shared, lower-level functions or data into a third module.

  • Check that the shared module does not import back into its users.

  • Check the revised import flow using the program's usual entry point and environment.

A few more useful answers

Does importing a specific name instead of a whole module always prevent a circular import?

No. It can clarify or narrow a dependency, but mutual imports during module initialization can still create a cycle. Check the full import path and actual initialization behavior. [S1]

Should every function used by two modules go into a shared utilities file?

No. Move code only when it is genuinely shared and fits a lower-level module. A catch-all file can obscure responsibilities rather than clarify them.

Sources

References used for this guide.

  1. [S1] Python 3.15 import system reference: packages and module loading
    docs.python.org · Checked

YOUR QUESTION. YOUR DETAILS.

Let’s work through your situation.

Get a tailored answer or help with your next task. Starts at $1, with a quote before you pay.

Ask your own question
← Back to Geez at Work