Python · beginner

Python ModuleNotFoundError: find the environment mismatch

ModuleNotFoundError means the active Python interpreter resolved an `import` statement but could not locate the named module or its parent package. This guide walks through environment-mismatch causes — wrong interpreter, misaligned virtualenv, missing install, sys.path gaps — and shows how to verify which interpreter pip wrote to before changing anything.

The symptoms

  • Traceback ending with `ModuleNotFoundError: No module named '<name>'` raised directly from an `import` line in your code.
  • The same import statement succeeds in a REPL or Jupyter notebook but fails when the same script is launched from a shell.
  • The package appears in `pip list` output, yet the importing script still raises ModuleNotFoundError for it.
  • The error appears only in CI, a Docker container, or one specific shell, but cannot be reproduced on a developer's local machine.

Likely causes

  • `python` and `pip` resolve to different interpreters on `$PATH`, so `pip install` wrote the package into a site-packages the running interpreter never searches.
  • The script is launched outside its intended virtualenv, so the active `sys.path` no longer includes that environment's `site-packages` directory.
  • The module is a local file or package and its parent directory is not on `sys.path`, or a missing `__init__.py` prevents Python from treating the directory as a package.
  • A local source file shadows a stdlib or third-party module of the same basename (for example `email.py`, `json.py`, `typing.py`, `requests.py`).
  • `PYTHONPATH` was unset, cleared, or overridden by a wrapper, removing a directory the import previously relied on.
  • An editable install (`pip install -e .`) was performed against a different interpreter than the one now executing the code.

First ten minutes

  1. 01Capture the exact traceback line and the module name Python claims it cannot find; do not paraphrase it before investigating.
  2. 02Print `sys.executable` and `sys.path` from inside the failing process so you see exactly which interpreter runs and which directories it searches.
  3. 03Run `pip --version` and `python -m pip --version` and compare their `location`/executable lines — divergence between them is the most common root cause.
  4. 04Confirm whether the package directory actually exists inside one of the `sys.path` entries; absence there is the strict definition of "not installed for this interpreter".
  5. 05Note how the script is invoked (`python script.py`, `python -m pkg.script`, IDE run configuration) because the launch mode changes which directory Python prepends to `sys.path`.

Evidence to collect

  • Full traceback, including the file path and line number of the `import` statement that raised.
  • Output of `python -c "import sys, json; print(sys.executable); print(json.dumps(sys.path, indent=2))"` run in the same shell that produces the error.
  • Output of both `pip show <package>` and `python -m pip show <package>` for direct side-by-side comparison.
  • `python --version` and `python -m pip --version` strings from the failing shell, including any `(python X.Y)` suffix pip prints.
  • For local-module failures: directory listing of the suspected parent package, including whether `__init__.py` is present.

Where to look

  • The interpreter's `site-packages`, typically `<env>/lib/pythonX.Y/site-packages` on POSIX or `<env>\Lib\site-packages` on Windows — derived from the Python version string, not assumed.
  • The virtualenv's own executable, `<env>/bin/python` (POSIX) or `<env>\Scripts\python.exe` (Windows), to confirm which interpreter the activation scripts actually selected.
  • The current working directory and the script's directory — Python adds the script's parent to `sys.path[0]` only when launched as a file, not as `-m`.
  • `PYTHONPATH` visible to the failing shell (`echo "$PYTHONPATH"` / `echo %PYTHONPATH%`) and any `.pth` files inside `site-packages` that inject extra paths at interpreter startup.
  • Project configuration that pins an interpreter — `.env`, `tox.ini`, `pyproject.toml`, IDE run configurations — for a mismatch against the shell's `python`.

Diagnostic steps

  1. 01Reproduce the failure with the exact command used in CI or production, then from the same shell run `which python` and `which pip`; record whether the two paths share a prefix.
  2. 02Inside the failing process, print `sys.executable` and `sys.path` and compare them to the `Location` line from `pip --version`; a prefix mismatch is the environment-mismatch signal.
  3. 03Run `python -m pip show <package>` — note the `-m`, which binds pip to that specific interpreter — and confirm the reported `Location` is inside one of the directories listed by `sys.path`.
  4. 04For local-module imports, locate the parent directory of the module file and verify it is on `sys.path`; for ordinary packages, the project root (parent of the top-level package directory) is the correct addition, not the module's own directory.
  5. 05Search the working directory and every entry of `sys.path` for files whose basename matches a stdlib module (`email`, `json`, `typing`, `socket`, `random`, `requests`) to rule out local shadowing.
  6. 06Repeat the diagnostic command pair (`sys.executable` / `python -m pip show <package>`) in each suspect interpreter until exactly one returns the package — that is the environment the script must run in.
  7. 07Re-run the original failing command from the corrected shell without modifying application code, and confirm the import resolves; only then consider code or `PYTHONPATH` changes.

Common mistakes

  • Running `pip install <pkg>` while a different virtualenv is active than the one used to launch the script; pip happily writes to a directory the script will never search.
  • Trusting the IDE's selected interpreter without confirming the terminal's `python` matches it — IDEs and shells frequently disagree, especially after a project re-open.
  • Patching `sys.path` inside the script to mask the environment problem, then shipping code that still raises in any other execution context.
  • Naming a local file after a stdlib or installed package (`requests.py`, `email.py`, `socket.py`) and being unable to import the real one because Python prefers the local file.
  • Assuming `python` and `python3` point at the same interpreter; on many distributions they do not, and only one of them carries the installed packages.

Safe fixes

  • Activate the correct virtualenv before installing and before running, then verify with `which python` and `which pip` returning paths with the same `<env_root>` prefix.
  • Install using `python -m pip install <package>` so pip is unambiguously bound to the interpreter that will execute the code.
  • For local-module imports, launch the script as `python -m package.module` from the project root, or add the project root to `PYTHONPATH` for that run only.
  • Rename any local file that shadows a stdlib or installed package, then re-test in a fresh shell so module caches from earlier imports do not mask the fix.
  • For stale or duplicated installs, run `python -m pip show <package>` in each suspect interpreter and uninstall from any interpreter that should not own the package, leaving exactly one owner.

Prove the fix

  1. 01The exact `import` statement that previously raised ModuleNotFoundError now completes without traceback when the original failing command is re-run end to end.
  2. 02`python -m pip show <package>` run from the corrected shell returns a `Version` and `Location` whose path is a subdirectory of `sys.executable`'s prefix.
  3. 03`python -c "import <package>; print(<package>.__file__)"` returns a path inside the intended `<env_root>` rather than an unexpected site-packages.
  4. 04The CI job, container build, or remote shell that previously failed completes the same script without the traceback after rebuilding from the pinned `requirements.txt` or lockfile.

Prevention and next steps

  • Use one virtualenv per project and activate it explicitly in every shell; document the run command in a Makefile or `tox.ini` so the interpreter is unambiguous to everyone.
  • Always install with `python -m pip` rather than bare `pip` to remove the cross-interpreter trap at the source.
  • Pin dependencies in `requirements.txt` or a lockfile and rebuild environments from scratch in CI so stale local installs cannot mask real breakage.
  • Add a smoke import check such as `python -c "import <package>"` to pre-commit or CI before running tests, so a missing package fails fast rather than mid-run.

Safe commands and checks

python -c "import sys, json; print(sys.executable); print(json.dumps(sys.path, indent=2))"
which python && which pip
python -m pip show <package>
pip --version
python --version
python -m pip install --dry-run <package>
find <env_root> -maxdepth 6 -type d -name "<package>"
echo "$PYTHONPATH"