ModuleNotFoundError: Fix Python Imports That Fail
ModuleNotFoundError means Python searched sys.path and found no module by that name.
20+ years shipping production Python across data and backend systems. Written from production experience, not tutorials.
- ✓Running Python scripts and installing packages with pip
- ✓Basic virtual environments and the command line
- ✓Familiarity with tracebacks and import statements
- The fix: install with the interpreter that runs your app via
python3 -m pip install, then confirm withpython3 -m pip show. - Check the mismatch with
which python3 && python3 -c "import sys; print(sys.executable)"; barepipoften belongs to a different environment. - Launch package code with
python3 -m pkg.modfrom the project root sosys.path[0]includes the package —python3 pkg/mod.pydoes not. - Rule out shadowing with
python3 -c "import; a local; print( .__file__)" random.pyoremail.pyhijacks the real module.
Think of Python's import system as a librarian with a fixed list of shelves: your script's folder first, then PYTHONPATH entries, then the site-packages stacks. ModuleNotFoundError means the librarian walked every shelf and the book wasn't there. Usually the book exists in a different branch — shelved downtown (one interpreter) while the librarian searched uptown (your venv).
You installed the package — you watched pip succeed — and Python still says it doesn't exist. ModuleNotFoundError: No module named 'requests' is the interpreter telling you it searched every directory it knows and found nothing by that name. The search list is sys.path: the script's folder, PYTHONPATH entries, and the environment's site-packages. When any link in that chain points somewhere unexpected, the import fails no matter how correctly you spelled it. This error fires in five familiar situations: pip installed into one interpreter while the app runs under another, a script launched as python pkg/mod.py instead of python -m pkg.mod so the package root never lands on the path, a PYTHONPATH that exists on your laptop but not the server, a local random.py or email.py shadowing the standard library, and a package missing its __init__.py on tooling that still expects one. Each shape has a one-command confirmation — which python3, python -m pip show, printing sys.path or a module's __file__ — and a permanent fix that stops it recurring. This article walks through the lookup mechanism, the interpreter-mismatch trap, the -m package context, stdlib shadowing, namespace packages, and how this error differs from its parent ImportError.
sys.path Missed: Where Python Looked Before Giving Up
Every import starts with a search, and sys.path is the list of places Python agrees to look. In order: the launching context (sys.path[0], covered in the -m section), entries from the PYTHONPATH environment variable, the standard library, and the current environment's site-packages. The import system asks each location's finder for the top-level module name, takes the first hit, and only when every finder declines does it raise ModuleNotFoundError — carrying the missing name on exc.name so your handler can read it programmatically. That search order explains most mysteries. The same import requests succeeds in one terminal and fails in another because the two interpreters own different site-packages folders, or because one launch put the project root on the path and the other didn't. Your first diagnostic is always a dump from inside the failing context: python3 -c "import sys, pprint; pprint.pp(sys.path)" shows exactly which shelves the librarian checked. Compare that against where the package actually lives (python3 -m pip show <pkg> prints Location:), and the gap — wrong environment, missing root, absent PYTHONPATH — is usually visible in seconds rather than hours.
sys.path there — the image build log describes a different filesystem than the crashed process sees.pip Installed It, Python Can't See It: The Interpreter Mismatch
The single most common cause of this error is also the most embarrassing: the package is installed, just not for the interpreter running your code. pip is not a universal installer — it's a script pinned to one specific Python. On a machine with a system Python, a Homebrew Python, and two virtual environments, pip, pip3, and python3 can easily belong to three different worlds. pip install requests succeeding while python3 -c "import requests" fails is the signature symptom, and it confuses people because both commands look authoritative. The fix is a habit change: never address pip directly. python3 -m pip install requests runs pip as a module inside that exact interpreter, so installer and importer cannot disagree. Diagnose the current mess with which -a python3 pip pip3 (multiple lines means multiple worlds) and python3 -m pip show <pkg> versus pip show <pkg> — when the two answers differ, you've found your split. Virtual environments sharpen the trap because an inactive venv leaves your shell on the system Python while your service file points at the venv, or vice versa. Spell the interpreter absolutely everywhere it matters: Dockerfiles, systemd units, cron lines.
pip install, rewrite it as python -m pip install with the project's interpreter. One habit removes the most common ModuleNotFoundError on teams.pip to whichever came first on PATH -- pin the absolute venv binary in every install line so the serving interpreter always receives the packages.python -m pip guarantees the installer and the importer are the same binary.Script vs -m: The Package Context That Makes Imports Work
How you launch the code rewrites the search list, which is why imports that work in the IDE die in production. Running python3 pkg/mod.py sets sys.path[0] to the pkg/ folder itself and leaves __package__ unset — the module has no idea it lives inside a package, so from pkg import helpers and any explicit relative import like from . import helpers both fail with ModuleNotFoundError. Running python3 -m pkg.mod from the project root instead sets sys.path[0] to the root directory and fills in __package__, so absolute and relative imports resolve exactly as the package author intended. IDEs hide this because they silently add the project root to the path before running, matching -m behavior without telling you. The rule for teams: pick one launch command, write it in the README, and use the identical command in systemd units, Docker CMDs, and CI steps. When a traceback shows No module named 'pkg' for your own top-level folder, don't reinstall anything — print sys.path[0], confirm it points at the script's folder instead of the root, and switch the launch to -m. Nine times out of ten that's the entire incident. A quick proof settles any debate: run the entry both ways from the project root and print sys.path[0] each time. The script form shows the package folder; the -m form shows the root. That one-line difference is the whole incident.
python pkg/mod.py command runs without that help -- standardize the -m launch in the README and every service file so all environments agree.python -m pkg.mod from the root so sys.path[0] and __package__ agree with your imports.A Local File Named random.py: Shadowing the Standard Library
The cruelest variant of this error isn't a missing module — it's the wrong module winning the race. Your script's own directory sits ahead of the standard library and site-packages on sys.path, so a local file named random.py, email.py, or json.py hijacks every import random in the process, including imports inside third-party libraries you didn't write. The symptoms feel haunted: random.randint suddenly doesn't exist, smtplib breaks with bizarre attribute errors, and the traceback points at your innocent utility file. New projects trip on test.py and requests.py too — the latter shadows the real HTTP library the moment someone adds it as a dependency. Diagnosis takes one line: python3 -c "import random; print(random.__file__)" shows exactly which file won. If the path points into your project, rename the shadow immediately (app_random.py, mailer.py) and purge bytecode caches, because a stale __pycache__/random.cpython-*.pyc can keep serving the shadow after the rename. Prevention belongs in CI: a lint step that fails the build when any top-level .py filename collides with the standard library list catches this before it ever ships. When the culprit file is found, check its git history too -- shadows often arrive as quick debug scripts that were never meant to be committed, and the fix includes deleting them outright.
__pycache__ keep crashing for another deploy -- always purge the cache folders in the same change.__init__.py or Not: Regular vs Namespace Packages
Packages come in two flavors, and the missing __init__.py only breaks one of them. A regular package is a folder with an __init__.py (even an empty one) that marks the directory as importable and runs first on import — the place for package-level setup. Delete that file and older tooling, installers, and some test runners stop seeing the folder as a package, producing ModuleNotFoundError for code that sits right there on disk. The fix is trivial: restore the file. Python 3 also supports namespace packages: folders without __init__.py whose portions across several sys.path entries merge into one logical package. That design serves split distributions (plugins, large frameworks), not everyday apps — reaching for it accidentally, by deleting init files or half-installing a project, buys confusing behavior where some submodules resolve and others don't. When your own package won't import, inspect the layout directly: ls pkg/__init__.py plus python3 -c "import pkg; print(pkg.__path__)" shows whether the package resolved and which directories contributed. For application code the policy is simple: every package folder keeps its __init__.py, and namespace mechanics stay a conscious choice, not an accident.
__init__.py and imports die on older runners while working locally. After any package move, run the pkg.__path__ check before merging.ModuleNotFoundError vs ImportError: Subclass, Circles, Broken Code
ModuleNotFoundError is a subclass of ImportError, and the distinction decides what your except clauses mean. The child fires in exactly one situation: the import system found no module by that name anywhere on sys.path. The parent covers everything else that can go wrong around imports — a circular import where module A needs B while B is still initializing A (surfacing as ImportError: cannot import name ... from partially initialized module), a found module that raises during its own execution, or from pkg import missing_name where the package exists but the name inside it doesn't. Conflating them causes real incidents: a broad except ImportError around an optional dependency swallows a circular-import failure and reports "package not installed," sending the team to reinstall what was never missing while the true cycle hides. The discipline is narrow catching — except ModuleNotFoundError for the optional-dependency fallback, and let genuine ImportErrors crash loudly so the cycle or the broken module gets fixed. Read the message shape too: No module named 'x' (child, absent) versus cannot import name 'y' from 'x' (parent, present-but-broken) point at opposite fixes.
except ImportError around an import reports a circular failure as missing, and the team reinstalls a healthy package -- keep the child exception in the fallback branch so real cycles crash loudly.The Rebuild That Installed 9 Packages Into the Wrong Python
ModuleNotFoundError: No module named 'requests'. Health checks failed on every pod, the load balancer drained the fleet in 3 minutes, and checkout traffic got 500s for 18 minutes. Pip's build log showed all 9 requirements installed successfully, which made the first hour of debugging genuinely confusing.pip install -r requirements.txt in the Dockerfile installed into the same interpreter that ran the app. Locally that was true — one system Python did everything. Nobody noticed the image had two: the base image's /usr/bin/python3 that owned pip, and the app venv at /opt/app/venv that ran gunicorn with 14 workers.pip install -r requirements.txt, which resolved to the base image's /usr/bin/python3, while the gunicorn service ran /opt/app/venv/bin/python. All 9 third-party packages landed in the base interpreter's site-packages, invisible to the venv. The previous image had worked only because an engineer once installed the same list into the venv by hand during a late-night debug session — undocumented, unrepeated, and wiped by the rebuild. All 14 workers crashed within 40 seconds of rollout, serving 500s for 18 minutes until the old image was restored.RUN /opt/app/venv/bin/python -m pip install -r requirements.txt, pinning installs to the serving interpreter. Second, the gunicorn unit gained ExecStart=/opt/app/venv/bin/python -m gunicorn app:server, spelling the interpreter absolutely so no PATH could swap it. Third, CI gained a smoke step running venv/bin/python -c "import requests, app" for all 9 third-party imports before the image ships. The rebuilt image deployed to all 14 workers in 6 minutes, and the import gate has caught 2 mismatches since.- Bare
pipis an address, not a guarantee. It names whichever interpreter installed that script — always spellpython -m pipwith the interpreter you serve. - Smoke-test imports with the serving binary in CI. Nine one-line imports would have caught this before any worker ever restarted.
- One image, one interpreter path. Absolute paths in Dockerfiles and unit files remove the entire class of PATH-dependent surprises.
ModuleNotFoundError but pip claims the package is installedwhich python3 && python3 -c "import sys; print(sys.executable); print(sys.prefix)" then python3 -m pip show <pkg>. If pip show finds it but python3 -m pip show doesn't, the package lives under a different interpreter — reinstall with python3 -m pip install <pkg>.which -a python python3 pip pip3 and compare with the service file's ExecStart line. Then run python3 -c "import sys; print(sys.executable)" as the service user (with sudo -u appuser if needed). When the service interpreter differs from your shell's, activate the venv in the unit file or use its absolute /opt/app/venv/bin/python.from pkg import x fails even though the package folder is right therepython3 -c "import sys, pprint; pprint.pp(sys.path)" and echo $PYTHONPATH. If the project root is absent, relaunch with python3 -m pkg.mod from the root so sys.path[0] becomes the working directory, or export PYTHONPATH=/opt/app/src in the service environment.random or email suddenly lacks attributes it always hadpython3 -c "import random; print(random.__file__)" (swap in your module name). If the path points inside your project instead of the stdlib or site-packages, list suspects with ls *.py and rename the shadow — random.py becomes app_random.py — then delete stale bytecode with find . -name '__pycache__' -type d -prune -exec rm -rf {} +.__init__.pyls pkg/__init__.py and python3 -c "import pkg; print(pkg.__path__)". If the init file is missing and imports fail on older tooling, restore an (even empty) __init__.py. If the layout is intentionally namespace-style, verify every contributor directory is installed rather than half-present.| File | Command / Code | Purpose |
|---|---|---|
| modulenotfound_sys_path.py | print("interpreter:", sys.executable) | sys.path Missed |
| modulenotfound_which_python.py | print("executable:", sys.executable) | pip Installed It, Python Can't See It |
| modulenotfound_run_context.py | print("__name__:", __name__) | Script vs -m |
| modulenotfound_shadow.py | print("random loads from:", random.__file__) | A Local File Named random.py |
| modulenotfound_packages.py | print("module file:", json.__file__) | __init__.py or Not |
| modulenotfound_vs_importerror.py | print("subclass:", issubclass(ModuleNotFoundError, ImportError)) | ModuleNotFoundError vs ImportError |
Key takeaways
exc.namepython -m pip; bare pip may belong to a different interpreter than your app.python -m pkg.mod from the root so sys.path[0] and __package__ are correct.print(mod.__file__) and rename it.__init__.py; namespace packages survive without one but confuse older tooling.ModuleNotFoundError for optional dependencies so circular imports still fail loudly as ImportError. Common mistakes to avoid
5 patternsInstalling with `pip` while the app runs under a different `python3`
pip install requests reports success, but the app crashes with ModuleNotFoundError: No module named 'requests' on the next deploy.python3 -m pip install requests. Verify with python3 -m pip show requests and python3 -c "import requests" before deploying.Running `python3 pkg/mod.py` and relying on sibling imports
ModuleNotFoundError: No module named 'pkg' because sys.path[0] is the script's folder, not the project root.python3 -m pkg.mod from the project root, or set PYTHONPATH to the root in the service unit.Naming a local file `random.py` or `email.py`
import random suddenly lacks randint, or import email breaks smtplib — the traceback points at your own file shadowing the standard library.mailer.py or app_random.py, delete the stray random.pyc, and rerun. Reserve stdlib names forever.Exporting `PYTHONPATH` on your laptop and forgetting it on the server
PYTHONPATH out of production service files. Install the project (pip install -e .) so the code resolves from site-packages on every machine identically.Catching broad `ImportError` to detect an optional dependency
ModuleNotFoundError for the optional-dependency branch and ImportError only where circular or broken-module failures are genuinely expected.Interview Questions on This Topic
What is sys.path and in what order is it searched?
sys.path in order — script directory, PYTHONPATH, stdlib, site-packages — asking each finder for the top-level module. The first hit wins; no hit raises ModuleNotFoundError carrying the missing name in exc.name. sys.path[0] depends on how you launched, which is why the same import works one way and fails another.Frequently Asked Questions
20+ years shipping production Python across data and backend systems. Written from production experience, not tutorials.
That's Errors. Mark it forged?
5 min read · try the examples if you haven't