Quick answer
- Already ran
pip install? Make sure thatpipbelongs to the same Python that runs your code — install withpython -m pip install X. - The import name often isn't the install name —
import sklearnneedspip install scikit-learn. - Works in the terminal but not in Jupyter/VS Code? Different kernel. Use
%pip installinside the notebook. - Got
; 'X' is not a packageon the end? It's your file shadowing something — see Fix 5.
The exact error string
Traceback (most recent call last):
File "app.py", line 1, in <module>
import requests
ModuleNotFoundError: No module named 'requests'
# a missing submodule quotes the full dotted path:
ModuleNotFoundError: No module named 'json.nosuchsub'
# and this suffix means something quite different — see Fix 5:
ModuleNotFoundError: No module named 'collections.abc'; 'collections' is not a package
Python raises this when an import cannot be resolved against sys.path — the ordered list of directories it searches. The important consequence: "not found" is a statement about this interpreter's search path, not about whether the package exists on your machine. It is entirely normal for a package to be installed, importable from one shell, and invisible to the interpreter that just failed.
Verified on CPython 3.14.6 throughout this page.
Anatomy of the message
The exception carries more than the text shows. Catching it gives you a machine-readable module name, which is useful in library code that wants to offer a helpful install hint:
try:
import some_missing_pkg
except ModuleNotFoundError as e:
print(type(e).__name__) # ModuleNotFoundError
print(repr(e.name)) # 'some_missing_pkg' <-- just the name
print(str(e)) # No module named 'some_missing_pkg'
print(isinstance(e, ImportError)) # True <-- it is a subclass
Two details decide which fix you need:
- A dotted name (
'json.nosuchsub') means the parent imported fine and the child is missing — a layout or version problem, not a missing install. - The
; 'X' is not a packagesuffix means Python found something namedX, but it was a plain module rather than a package it can import from. Sometimes that is legitimate —import os.nonexistentreports'os' is not a packagebecauseosgenuinely is a module, not a package. But whenXis a name you recognise from your own project, it is your file shadowing something. Jump to Fix 5.
Triage: three commands, in this order
Step 1 is non-negotiable. Almost every "but I already installed it" ends at step 2 with the package installed somewhere the running interpreter never looks.
# 1. Which interpreter is running? Put this in the FAILING script/notebook.
python -c "import sys; print(sys.executable)"
# 2. Ask THAT interpreter whether it has the package (use its full path if needed)
python -m pip show requests
python -m pip --version # prints "... (python 3.14)" — the interpreter pip serves
# 3. Still stuck? See exactly which directories are searched, in order
python -c "import sys; print(*sys.path, sep='\n')"
Fix 1: pip and python are different interpreters
This is the single most common cause, and the reason "I already installed it" is such a frequent complaint. A bare pip is resolved through PATH and belongs to whichever Python installation happens to come first — not necessarily the one running your code. Install using the interpreter path you captured in triage step 1, not whatever python means in your current shell:
# ❌ ambiguous — whose pip is this?
# pip install requests
# ✅ the reliable form: install with the EXACT interpreter that failed
# (paste the path sys.executable printed in triage step 1)
/path/to/that/python -m pip install requests
# ✅ shorthand — only once you've confirmed `python` IS that interpreter:
python -m pip install requests
Confirm that before trusting the shorthand — python -m pip --version ends with the interpreter it serves, for example pip 26.1.2 from .../site-packages/pip (python 3.14). If that doesn't match the sys.executable from triage step 1, the bare python in your shell is not the one running your code, and only the full-path form will help. This matters most where the two are guaranteed to differ: notebooks, IDE run configurations, cron jobs and systemd units.
Fix 2: the import name isn't the install name
The error always quotes the name you imported, which frequently is not the name you install. This is why pip install sklearn is such a persistent trap: it is a deprecated placeholder package, and the real library is scikit-learn. Every pair below is real:
| You wrote | You must install |
|---|---|
import sklearn | pip install scikit-learn |
import cv2 | pip install opencv-python |
import PIL | pip install Pillow |
import yaml | pip install PyYAML |
import bs4 | pip install beautifulsoup4 |
import dateutil | pip install python-dateutil |
import serial | pip install pyserial |
import docx | pip install python-docx |
import jwt | pip install PyJWT |
import Crypto | pip install pycryptodome |
When in doubt, search the project on PyPI rather than guessing from the import. A wrong guess sometimes succeeds — installing an unrelated or squatted package — which is worse than failing. If your import is a JWT library, note that import jwt is satisfied by PyJWT; the JWT signature errors reference covers what usually goes wrong next.
Fix 3: you're using the wrong virtual environment
The common case is an unactivated venv: you type python, get the system interpreter, and the package installed in .venv is invisible to it. Worth being precise about the mechanism though — activation is a convenience, not a requirement. All that matters is that the script runs with the Python executable inside the environment where the package is installed, which is why .venv/bin/python app.py works perfectly well with nothing activated. Activation just puts that executable first on your PATH.
# does sys.prefix differ from sys.base_prefix? then a venv IS active
python -c "import sys; print(sys.prefix); print(sys.base_prefix)"
# activate, then verify the interpreter moved
source .venv/bin/activate # macOS / Linux
.venv\Scripts\activate # Windows
python -c "import sys; print(sys.executable)"
A related trap on Debian, Ubuntu and Homebrew Python: installing into the system interpreter is now blocked outright, which surfaces as a different error entirely — see error: externally-managed-environment. The correct response there is a virtual environment, not --break-system-packages.
Fix 4: notebooks and editors run a different interpreter
"It works in the terminal but not in Jupyter" is one of the most-reported forms of this error, and it is always the same thing: the kernel is a different Python. Do not guess — ask the kernel directly, from inside a cell:
# run this IN THE NOTEBOOK CELL, not the terminal
import sys
print(sys.executable) # compare against your terminal's sys.executable
# install into the running kernel — the %pip magic targets it correctly
%pip install requests
Use %pip rather than !pip. The ! form shells out and hits the same PATH ambiguity as Fix 1, while the magic is routed to the kernel's own interpreter. In VS Code, the interpreter is chosen per-workspace through the interpreter picker; selecting the environment you installed into fixes it without touching any code.
Fix 5: your own files are shadowing the module
Python puts the running script's own directory first on sys.path. A local file therefore wins over an installed package or even the standard library. Name a file json.py and every import json in that directory gets your file:
# project/json.py <-- an innocent-looking file name
# project/main.py:
import json
json.dumps({"a": 1})
# AttributeError: module 'json' has no attribute 'dumps' (consider renaming
# '.../project/json.py' since it has the same name as the standard library
# module named 'json' and prevents importing that standard library module)
That parenthetical hint is a recent CPython nicety — verified on 3.14.6. On older versions you get the bare AttributeError with no explanation at all, which is why this cause has a reputation for wasting whole afternoons. If you shadow a package rather than a module, you get our error instead, with the tell-tale suffix:
# project/collections.py exists, then:
import collections.abc
# ModuleNotFoundError: No module named 'collections.abc'; 'collections' is not a package
Rename the offending file. If Python keeps resolving the old module afterwards, delete the __pycache__ directory beside it — a leftover .pyc can outlive the .py and keep the shadow alive. To clear them all at once:
# run from your project root — both forms delete recursively
# macOS / Linux
find . -name '__pycache__' -type d -exec rm -rf {} +
# Windows PowerShell
Get-ChildItem -Path . -Filter __pycache__ -Recurse -Directory | Remove-Item -Recurse -Force
The same first-entry rule explains a related puzzle: sys.path[0] is the script's directory when you run a file, but the empty string (meaning the current directory) under python -c and the REPL — so an import can genuinely succeed interactively and fail as a script from a different working directory.
Fix 6: it's a standard library module that isn't there
pip cannot help when the missing module belongs to the standard library. There are two distinct reasons, and they have different fixes:
| Module | Why it's missing | Fix |
|---|---|---|
fcntl, pwd, termios, grp | Unix-only; they do not exist on Windows | Guard the import per-platform, or run under WSL |
_lzma, _bz2, _ssl, _sqlite3 | Optional C extensions, only built when the matching system library and headers were present at compile time — a classic pyenv gap | Install the dev package (e.g. liblzma-dev) and rebuild that Python version |
The give-away for the second row is that the name starts with an underscore: those are C accelerator modules, so a missing one means the interpreter itself was built incomplete. Reinstalling packages will never fix it.
Related errors, and how to tell them apart
| Message | Exception | Means |
|---|---|---|
No module named 'X' | ModuleNotFoundError | The module wasn't found on sys.path |
No module named 'X.Y'; 'X' is not a package | ModuleNotFoundError | X resolved to a plain module — usually your own file |
cannot import name 'Y' from 'X' | ImportError | Module loaded fine; the name inside it is missing |
module 'X' has no attribute 'Y' | AttributeError | Import succeeded, attribute access failed — often shadowing |
ModuleNotFoundError has been a subclass of ImportError since Python 3.6, so except ImportError still catches it — useful for optional-dependency fallbacks. The third row is a genuinely different problem; when it names a partially initialized module, you are looking at a circular import rather than a missing package. If you are reading a raw traceback rather than a clean terminal, paste it into the Error Log Analyzer and it will route you to the right page.
Debugging checklist
- ✓
print(sys.executable)inside the failing program — before anything else - ✓ Install with
python -m pip install, never a barepip - ✓ Confirm
python -m pip --versionnames the same interpreter - ✓ Check the install name really is the import name (
sklearn→scikit-learn) - ✓ In a notebook, use
%pip installand check the kernel's ownsys.executable - ✓ Venv active? Compare
sys.prefixwithsys.base_prefix - ✓ Suffix says
is not a package? Look for your own file with that name - ✓ Clear
__pycache__after renaming a shadowing file - ✓ Underscore-prefixed stdlib module? Rebuild Python with the dev headers — pip can't fix it
Frequently Asked Questions
I ran pip install and Python still says No module named. Why?
Because pip and python are almost certainly two different interpreters. A bare pip command resolves through PATH and may belong to a completely different Python installation than the one running your script. Print sys.executable in the failing script, then install with that exact interpreter using python -m pip install, which guarantees the package lands where the running interpreter will look for it. Running python -m pip --version confirms which interpreter pip is attached to.
Why does pip install sklearn not fix No module named 'sklearn'?
Because the name you import is not always the name you install. The library is published as scikit-learn and imported as sklearn, so the correct command is pip install scikit-learn. The same mismatch catches cv2 (install opencv-python), PIL (install Pillow), yaml (install PyYAML), bs4 (install beautifulsoup4) and dateutil (install python-dateutil). The error always quotes the import name, never the install name.
What is the difference between ModuleNotFoundError and ImportError?
ModuleNotFoundError is a subclass of ImportError, added in Python 3.6, raised specifically when the module itself cannot be located. If the module is found but a name inside it is not, you get a plain ImportError reading "cannot import name X from Y" followed by the file path. That distinction tells you where to look: ModuleNotFoundError is an environment or path problem, while "cannot import name" means the module loaded and the attribute is genuinely absent, misspelled, or unavailable in that version.
Why does it work in the terminal but not in Jupyter or VS Code?
The notebook kernel or editor interpreter is a different Python from your terminal. Run import sys; print(sys.executable) inside the notebook cell itself and compare it to the terminal — they will differ. Install from within the notebook using the %pip install magic, which targets the running kernel, rather than a terminal pip. In VS Code, use the interpreter picker to select the environment you actually installed into.
Why does my own file cause No module named?
Because the directory of the script you run is placed first on sys.path, so a local file shadows any installed module with the same name. A file called json.py in your project directory is imported instead of the standard library json, and a folder without __init__.py that Python treats as a plain module produces the distinctive message "No module named X.Y; X is not a package". Rename the offending file and delete its stale .pyc cache.
How do I see where Python is actually looking for modules?
Print sys.path from inside the failing program: python -c "import sys; print(sys.path)". It lists every directory searched, in order. The first entry is the script's own directory when running a file, or the empty string meaning the current directory when using python -c or the REPL — which is why the same import can succeed interactively and fail as a script. If the directory containing your package is not in that list, the import cannot succeed no matter what is installed.
Why is a standard library module like _lzma or fcntl missing?
Two different reasons. Modules such as fcntl, pwd and termios are Unix-only and simply do not exist on Windows, so no installation will provide them. Modules such as _lzma, _bz2 and _ssl are optional C extensions that are only built if the matching system library and headers were present when Python was compiled — a common gap with pyenv builds. Installing the development package such as liblzma-dev and rebuilding that Python version restores them; pip cannot help in either case.
References
- The import system (Python Language Reference)
- sys.path (Python Standard Library)
- ModuleNotFoundError (Built-in Exceptions)
- Installing Packages (Python Packaging User Guide)
More Python errors
Browse the full reference — exact message, cause, and fix — or paste a traceback and let the analyzer find the match.