Skip to content

Python ImportError: Attempted Relative Import Fix

ImportError: attempted relative import with no known parent package happens when you run a package file as a script. Here's the exact fix.

Python python-errors importerror imports packaging sys-path cpython python-3-14
Bharath G
Reading Progress

On This Page

1. The Error

Traceback (most recent call last):
  File "/tmp/repro/pkg/main.py", line 1, in <module>
    from . import utils
ImportError: attempted relative import with no known parent package

No caret markers, no "Did you mean" hint, no __cause__/__context__ chain — this is a flat, single-frame ImportError raised the moment the interpreter tries to resolve from . import utils. The message text is identical on 3.9, 3.10, 3.11, 3.12, and 3.13; I ran the exact reproduction below on 3.10.20, 3.12.3, and 3.13.13 and got byte-for-byte the same traceback on all three. PEP 657's fine-grained error locations (3.11+) don't add anything here because the entire statement — not a sub-expression inside it — is what fails, so there's nothing narrower to point a caret at.

You'll also see the older, near-identical wording depending on how deep the relative import is:

ImportError: attempted relative import beyond top-level package

That's the same root cause (Python can't place the current module inside a package hierarchy) tripped by a from .. or deeper dotted import instead of a single from ..

2. How to Reproduce It

Directory layout:

myproject/
└── pkg/
    ├── __init__.py
    ├── utils.py
    └── main.py
# pkg/utils.py
def helper():
    return "helper result"
# pkg/main.py
from . import utils

def run():
    print(utils.helper())

if __name__ == "__main__":
    run()
# pkg/__init__.py
# (empty)

Run it the way almost everyone does first — by pointing the interpreter straight at the file:

$ cd myproject
$ python pkg/main.py
Traceback (most recent call last):
  File "/tmp/repro/pkg/main.py", line 1, in <module>
    from . import utils
ImportError: attempted relative import with no known parent package

It also fails identically if you cd pkg first and run python main.py from inside the package directory — the problem isn't your current working directory, it's how the file was launched.

Compare that to running it as a module with -m, from the project root:

$ python -m pkg.main
helper result

Same file, same import, no environment variables changed — only the invocation differs, and that's the entire bug.

3. Version Behaviour Matrix (Python 3.11 / 3.12 / 3.13 / 3.14 / 3.15)

This error is version-neutral — the mechanism (script execution has no parent package) hasn't changed since relative imports were introduced by PEP 328, and it won't change going forward. What has moved around it:

VersionBehaviorNotes
3.9 – 3.10Same error, same wording3.9 is EOL (2025-10); 3.10 is security-only, EOL 2026-10 per devguide.python.org/versions
3.11Same errorAdds -P / PYTHONSAFEPATH (see below) — doesn't fix this, but changes a related failure mode
3.12Same errordistutils removal (PEP 632) is unrelated but often hits the same "packaging layout" projects
3.13Same errorConfirmed identical traceback in this article's repro
3.14 (current bugfix, released 2025-10-07)Same errorNo change to import statement resolution for this case
3.15 (feature-frozen; 3.15.0rc2 scheduled 2026-09-01, final 2026-10-01)Same errorPEP 810 adds explicit lazy import syntax, but lazy imports still resolve relative imports through the same package-context machinery — a lazy from . import utils inside a script with no parent package fails the same way, just later (on first access instead of at the lazy import line)

The one thing worth flagging for 3.11+: the -P flag and PYTHONSAFEPATH variable (added in 3.11) stop Python from prepending the current directory to sys.path for -c, the REPL, and — critically — for -m itself. That means python -P -m pkg.main run from outside the project root can turn this into ModuleNotFoundError: No module named 'pkg' instead, because pkg was never importable in the first place. -P is a security hardening flag, not a relative-import fix.

4. Why It Happens — Surface Level

Relative imports (from . import x, from .. import y) aren't resolved relative to a file's location on disk — they're resolved relative to the importing module's package. When you run python pkg/main.py, Python doesn't import pkg.main as a submodule of pkg; it executes main.py directly and calls the resulting module __main__. A module named __main__ has no package, so from . import utils has no package to be "relative to," and CPython refuses to guess.

5. Why It Happens — Under the Hood

Every module CPython runs carries two identity markers: __name__ and __package__ (backed by __spec__ when the module was found through the import system). Compare the two invocations from the reproduction:

$ python pkg/show_ctx.py
__name__ = __main__
__package__ = None
__spec__ = None

$ python -m pkg.show_ctx
__name__ = __main__
__package__ = pkg
__spec__ = ModuleSpec(name='pkg.show_ctx', loader=<_frozen_importlib_external.SourceFileLoader ...>, origin='/tmp/repro/pkg/show_ctx.py')

Both runs report __name__ == "__main__" — that part is identical, which is why "check if __name__ == "__main__"" is not the fix people think it is here. The difference that actually matters is __package__ and __spec__.

python -m pkg.show_ctx goes through runpy, which uses importlib to find pkg.show_ctx as a real module inside the pkg package: it walks sys.meta_path, gets a PathFinder to build a ModuleSpec with name='pkg.show_ctx', and derives __package__ from that spec (spec.parent, which is pkg). python pkg/show_ctx.py skips all of that — the interpreter just compiles and executes the file's source directly as the top-level script, assigns it the synthetic name __main__, and there is no ModuleSpec at all, so __package__ ends up None.

When the bytecode for from . import utils runs, IMPORT_NAME calls __import__("utils", globals(), locals(), (), 1) — that trailing 1 is the relative-import level (one dot). The import system's job is then to resolve level 1 against globals()['__package__']. With __package__ is None (and no dots to walk in __name__, since it's just "__main__"), importlib._bootstrap._calc___package__ and the relative-import resolution in _find_and_load/_sanity_check have nothing to resolve against, so they raise ImportError: attempted relative import with no known parent package before ever consulting sys.path or sys.meta_path for utils itself. Note what this means: the error fires during package resolution, not during the search for utils.pyutils.py sitting right next to main.py on disk is irrelevant, because the relative import never gets far enough to look for it.

This is also why sys.path[0] matters for the fix even though it isn't the cause of the error. For python pkg/main.py, sys.path[0] is set to the script's own directory (.../myproject/pkg), so an absolute import utils would work — the error is specifically about the relative-import machinery, not about whether utils is reachable at all. For python -m pkg.main, sys.path[0] is instead the current working directory (myproject), which is what lets pkg itself be found as a package in the first place. You can see this directly:

$ python pkg/show_path.py
sys.path[0] = /tmp/repro/pkg

$ python -m pkg.show_path
sys.path[0] = /tmp/repro

6. The Fix

Quick fix — run it as a module, not a script:

- python pkg/main.py
+ python -m pkg.main

Run from the project root (the directory containing pkg/), not from inside pkg/. This is a zero-code-change fix and is correct for throwaway scripts and quick local runs.

Correct fix for anything that ships — install it as a real package so there's no "run it right" convention to remember or explain in a README:

# pyproject.toml
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "pkgdemo"
version = "0.1.0"
requires-python = ">=3.10"

[project.scripts]
pkgdemo = "pkg.main:run"
python -m venv .venv
source .venv/bin/activate
pip install -e .
pkgdemo          # works from anywhere
python -m pkg.main    # also works from anywhere now

I verified both entry points against the reproduction above — pip install -e . followed by the generated pkgdemo console script, and python -m pkg.main run from /tmp (outside the project entirely), both print helper result. Once the package is installed (even in editable mode), pkg is importable from any working directory, so __package__ resolves correctly no matter how the entry point is launched.

What doesn't fix it: adding sys.path.insert(0, "..") hacks, converting the relative import to try/except ImportError fallback imports, or wrapping the file in if __name__ == "__main__": — none of these change __package__, which is the actual value the import system needs.

7. Best Practices & The Better Design

The underlying anti-pattern is treating a package's internal module as something you run by path. Once a project has more than one file that imports from a sibling, stop running files directly and give the project a real entry point:

  • Use a src/ layout (src/pkg/...) plus pip install -e ., so there is exactly one way to get pkg on sys.path and it's the same in dev, CI, and production.
  • Expose behavior through [project.scripts] console entry points instead of "run this specific file with python."
  • If you need a script-like top level, put a thin __main__.py in the package (pkg/__main__.py) so python -m pkg works without needing to know the submodule name.
  • Never reach for sys.path.insert or PYTHONPATH=. in application code as a substitute for installing the package — it papers over the exact mechanism described above and breaks again the moment someone runs the file a different way (a different CWD, a different IDE run configuration, a test runner).
# pkg/__main__.py
from .main import run

if __name__ == "__main__":
    run()
python -m pkg   # now works too

8. How to Prevent It Long-Term

  • Add a lint rule that flags relative imports outside an installed package layout — flake8-tidy-imports' TID252 (relative imports banned) is one option if your team prefers explicit absolute imports everywhere; if you keep relative imports, the real guard is structural, not lint-based.
  • Put python -m pkg.main (or the installed console script) in the README's "how to run this" section instead of a bare file path — most instances of this error are simply someone following stale or missing run instructions.
  • In CI, run the project the same way real users will run it: pip install -e . then the console script or python -m pkg, never python pkg/some_file.py, so a regression in packaging surfaces in CI instead of in a new contributor's terminal.
  • If your IDE (PyCharm, VS Code) "just works" on this file but the CLI doesn't, that's not a Python inconsistency — the IDE is silently running your file as -m under the hood or mutating sys.path in its run configuration. Check the IDE's run configuration and mirror the same invocation in your terminal and CI, rather than trusting the IDE run button as the ground truth.
  • This error sits right next to two others worth knowing the boundary of: ModuleNotFoundError: No module named 'x' (the package isn't importable at all — wrong interpreter or missing install) and ImportError: cannot import name 'X' from partially initialized module (a circular import between two modules that are both correctly on sys.path). All three get pasted into search bars with nearly the same words, but the fixes don't overlap — check which one you actually have before applying a fix from the other two.

9. Key Takeaways

  • ImportError: attempted relative import with no known parent package means the running module's __package__ is None — it fires during package-resolution, before Python even looks for the imported name on disk.
  • python pkg/main.py executes the file as __main__ with no package context; python -m pkg.main resolves pkg.main through importlib and sets __package__ correctly. Same file, same import, different outcome.
  • The fast fix is python -m pkg.main from the project root; the durable fix is pip install -e . so the package is always importable regardless of how it's launched.
  • -P / PYTHONSAFEPATH (3.11+) can turn this into a different error (ModuleNotFoundError) if you combine it with -m from outside the project root — it's a security flag, not a relative-import fix.
  • Don't reach for sys.path hacks or try/except import fallbacks — they hide the real cause and tend to break again under the next runner (IDE, test framework, packaging tool) that launches the file differently.
Pythonpython-errorsimporterrorimportspackagingsys-pathcpythonpython-3-14

From aspiring developer to blogger, I test learning platforms, simplify programming syntax, and share resources that work. Helping you code smarter as I grow myself. New or experienced, you're welcome here.

Comments