Why does `from . import config` fail with 'attempted relative import with no known parent package'?
answer
- The dots need a starting point
- It depends on how the module was loaded
- Not the directory, not the __init__.py
- Empty or missing package name in globals
- __package__ is None for a run file
basics
~20 sLeading dots resolve against the importing module's package name in package. A module that Python did not load as part of a package has no package name, so there is nothing to count outward from and the import machinery raises ImportError.
solid answer
~50 sA relative import builds an absolute module name by joining the importing module's own package name with whatever follows the dots. That package name is read from `__package__` in the module's globals. When a file is executed directly, it is loaded as `__main__` with `__package__` set to `None` on 3.14, so there is nothing for the dot to count from and the machinery raises `ImportError` rather than guessing from the directory layout. The same error appears in the REPL and in a module imported as a *top-level* module, where `__package__` is the empty string — it is about how the module was loaded, not where the file sits. The fix is to have the module imported as part of its package, or to use an absolute import. Patching `sys.path` or assigning `__package__` by hand silences it while risking the same module being loaded twice under two names.
code
python · 26 linesimport pathlib
import subprocess
import sys
import tempfile
root = pathlib.Path(tempfile.mkdtemp())
pkg = root / "app"
pkg.mkdir()
(pkg / "__init__.py").write_text("")
(pkg / "config.py").write_text("VALUE = 1\n")
(pkg / "main.py").write_text(
"print('__package__ =', repr(__package__))\n"
"from . import config\n"
"print('imported', config.VALUE)\n"
)
def run(*args):
return subprocess.run([sys.executable, *args], cwd=root,
capture_output=True, text=True)
direct = run(str(pkg / "main.py"))
print(direct.stdout.strip()) # __package__ = None
print(direct.stderr.strip().splitlines()[-1]) # ImportError: attempted ...
print(run("-m", "app.main").stdout.strip()) # __package__ = 'app' / imported 1go deeper
Recognise the message and know the one-line cause: the file was run directly, so it has no package for the dots to count from. Know that the quick escape is an absolute import.
Explain the resolution rule: the machinery joins __package__ with the name after the dots, and a directly loaded module has None there. Be able to say why the directory layout and __init__.py do not change it.
Diagnose it as a packaging-shape problem, not a syntax problem: say why a sys.path patch risks the same module being loaded twice under two names, and restructure so the module is always imported through its package.
Set the rule for the codebase: one documented way to start every program, so that no module is ever both an entry point and an imported member of a package, and so the failure cannot be worked around locally by one team.
### What the error is actually reporting `from . import config` is not a filesystem operation. The leading dot tells the import machinery to build an absolute module name by taking the **package name of the module doing the importing** and appending what follows the dots. That package name comes out of the importing module's own globals: `__package__`, the value the module's `__spec__` reports as its parent when the module was imported normally. If that value is `None` or the empty string, there is no left-hand side for the join, the machinery has nothing to resolve, and it refuses rather than guessing — `ImportError: attempted relative import with no known parent package`. The single-sentence version to say out loud: *the module was not imported as part of a package, so it has no package name for the dots to count from.* ### The three situations that produce it **A file executed directly as the top-level script.** The interpreter binds it as `__main__`, and on 3.14 both `__package__` and `__spec__` are `None`. Its position on disk is irrelevant: putting the file inside a directory that contains `__init__.py` does not change how it was *loaded*, and loading is what determines the package name. This is the overwhelmingly common case — someone runs one file of a package to try it out and hits the error immediately. **Code with no module identity.** The REPL, `python -c`, and anything handed to `exec` with a fresh globals dict have no package name either, so the same relative import fails there for the same reason. **A module imported as a top-level module.** If the directory itself is on the import path and a file is imported as `rel` rather than as `app.rel`, its `__package__` is `''` — an empty package name. One dot has nothing to count from, and the identical error appears even though nothing was "run as a script". You can prove the mechanism has nothing to do with files by driving it through `exec`: with `{"__name__": "standalone"}` as globals, `from . import decoder` fails; add `"__package__": "json"` to the same dict and it imports `json.decoder`. The dictionary key is the whole input. ### The fixes, and the fix that only looks like one The real fix is to make the module be imported as part of its package. Import it from another module of the package, or start the program at the package's entry point rather than at the leaf file, so the module arrives with a package name and the dots resolve. Alternatively, use an absolute import — `from app import config` — which needs no package name in the importing module, only for `app` itself to be findable on the import path. The non-fix is the one that appears in most answers found by pasting the error into a search box: appending the parent directory to `sys.path` and switching to absolute imports, or worse, assigning `__package__` by hand at the top of the file. That can silence the message while leaving the module importable under two different names — once as `__main__` and once as `app.main` — which gives you two copies of every module-level object it defines: two class objects that fail `isinstance`, two module-level caches, two registries that each hold half the entries. The error is honest; the path hack converts a loud failure into a quiet one. Adding a missing `__init__.py` is neither fix nor non-fix on its own. It matters for how the directory can be imported, but it does not retroactively change how the already-running module was loaded — the file you ran directly is still `__main__` with no package. ### The sibling error worth knowing `ImportError: attempted relative import beyond top-level package` is the other half of the pair. Here the package name *is* known, but the dots count up past it: inside `app.services`, `from ...util import x` asks for two levels above `app`, and there is no name left. Note what does not happen — the machinery does not look at the parent directory on disk to see whether it might be a package. The whole computation is string arithmetic on the package name, which is exactly why moving a file between packages silently changes what its relative imports mean while moving the whole package does not. ### Answering it well in an interview State the rule (dots resolve against `__package__`), name the trigger (the module was run or loaded without a package), then say what you would change (import through the package, or use an absolute name) and what you would refuse to do (mutate `sys.path` or `__package__` to paper over it, because of double-import). Mentioning that the same error appears for a plain top-level module — not only for a directly run script — is the detail that shows you understand it as name resolution rather than as a rule about scripts.
- How does that error differ from 'attempted relative import beyond top-level package'?The second one means the package name *is* known, but the dots count up past it. Inside `app.services`, `from ...util import x` asks for two levels above `app` and runs out of name. It is still pure string arithmetic on the package name — the machinery never inspects the parent directory to decide whether it could be a package.
- Does adding a missing `__init__.py` beside the file fix the error?Not by itself. `__init__.py` affects how a directory can be imported as a package; it does not change how the module currently running was loaded. A file executed directly is still `__main__` with no package name, whatever files sit next to it. What has to change is that the module is imported under its dotted name.
- Why is appending the parent directory to `sys.path` a poor fix?Because it makes the same file importable under two names — as `__main__` and as `app.main` — and each import creates a separate module object. Classes defined in it then exist twice and fail `isinstance` checks against each other, and module-level caches or registries split in half. The error was pointing at a real structural problem, and the path hack converts it into a silent one.
- Can the error appear for a module nobody ran as a script?Yes. If the directory itself is on the import path and the file is imported as a top-level module, its `__package__` is the empty string, and one dot still has nothing to count from. That case is a good reminder that the rule is about the module's loaded identity, not about scripts.
The dots are like saying "two doors down" without stating which street you are on. Once the module is loaded as part of a package it knows its street; run it as a bare file and the instruction has no starting point, so the machinery refuses to guess.
saying these in an interview costs you the question
- Blames a missing __init__.py for the error on its own
- Says the file is in the wrong directory
- Thinks the dots resolve against the current working directory
- Fixes it by appending the parent directory to sys.path
- Assigns __package__ by hand at the top of the file
- Believes the error means the imported module does not exist