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
- 01Capture the exact traceback line and the module name Python claims it cannot find; do not paraphrase it before investigating.
- 02Print `sys.executable` and `sys.path` from inside the failing process so you see exactly which interpreter runs and which directories it searches.
- 03Run `pip --version` and `python -m pip --version` and compare their `location`/executable lines — divergence between them is the most common root cause.
- 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".
- 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
- 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.
- 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.
- 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`.
- 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.
- 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.
- 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.
- 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
- 01The exact `import` statement that previously raised ModuleNotFoundError now completes without traceback when the original failing command is re-run end to end.
- 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.
- 03`python -c "import <package>; print(<package>.__file__)"` returns a path inside the intended `<env_root>` rather than an unexpected site-packages.
- 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"