Import Python modules from another directory

Organize code as an importable package, install it for development, or load a trusted file explicitly when ordinary package imports are not appropriate.

Best practices
Python programming and application logging

For project code, prefer a package import or an editable installation. Changing sys.path can help a short script, but a stable package layout avoids depending on how an IDE or shell happened to start Python.

Import through the package layout

project/
    main.py
    utils/
        __init__.py
        helpers.py
    scripts/
        __init__.py
        tool.py

If helpers.py defines my_function, either main.py or scripts/tool.py can use:

from utils.helpers import my_function

From project, run the tool as a module:

python -m scripts.tool

The directory containing the top-level packages must be discoverable. Adding __init__.py alone does not make an arbitrary parent directory appear on the import path.

Install the project in the active environment

For a project with valid packaging metadata and package discovery configured in pyproject.toml:

python -m pip install -e .

An editable install makes its declared packages importable from that environment while keeping development source editable. The mere presence of a pyproject.toml is not sufficient: its build backend and packaging configuration must support the project. An unrelated working-directory file can still shadow an installed module.

Add a path for a small script

import sys
sys.path.append("/path/to/utils")
import helpers

Replace that path with a real trusted directory. An absolute path avoids dependence on the working directory, but remains machine-specific. Appending puts the directory after existing entries, so an earlier helpers can win. Prepending changes precedence and can shadow other packages; neither operation is a substitute for a deliberate package name.

Load a trusted file by path

import importlib.util
import sys

def load_file(name, path):
    if name in sys.modules:
        raise ValueError(f"Module already loaded: {name}")
    spec = importlib.util.spec_from_file_location(name, path)
    if spec is None or spec.loader is None:
        raise ImportError(f"Cannot create an import spec for {path}")
    module = importlib.util.module_from_spec(spec)
    sys.modules[name] = module
    try:
        spec.loader.exec_module(module)
    except BaseException:
        sys.modules.pop(name, None)
        raise
    return module

# Supply an existing trusted file:
# helpers = load_file("project_helpers", "/path/to/utils/helpers.py")
# helpers.my_function()

This executes the file. It is not a sandbox, and registration cleanup cannot undo the module's side effects. A standalone name also does not provide the package context needed for arbitrary relative imports inside that file.

FAQ

Why does importing work only in the IDE? Compare interpreter selection, editable installations, working directory, and sys.path. The IDE may add a source directory that the command line never sees.

Why does from ..utils fail? The dots refer to package ancestry, not arbitrary parent directories. Run an importable package module with -m and use a layout that actually has the required parent package.

How do I identify a name collision? Check getattr(module, "__file__", None) and module.__spec__. Some built-in or namespace modules do not have a conventional file path.

References

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting