Python: ModuleNotFoundError: No module named 'X'

Quick answer

  • Already ran pip install? Make sure that pip belongs to the same Python that runs your code — install with python -m pip install X.
  • The import name often isn't the install name — import sklearn needs pip install scikit-learn.
  • Works in the terminal but not in Jupyter/VS Code? Different kernel. Use %pip install inside the notebook.
  • Got ; 'X' is not a package on 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:

Triage: three commands, in this order

import sys; print(sys.executable) which interpreter is actually running? <that python> -m pip show <package> is it installed for THAT interpreter? not installed installed Install it for that interpreter check install name != import name Path problem, not install print sys.path shadowing file? wrong venv? package dir not on the path?

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 wroteYou must install
import sklearnpip install scikit-learn
import cv2pip install opencv-python
import PILpip install Pillow
import yamlpip install PyYAML
import bs4pip install beautifulsoup4
import dateutilpip install python-dateutil
import serialpip install pyserial
import docxpip install python-docx
import jwtpip install PyJWT
import Cryptopip 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:

ModuleWhy it's missingFix
fcntl, pwd, termios, grpUnix-only; they do not exist on WindowsGuard the import per-platform, or run under WSL
_lzma, _bz2, _ssl, _sqlite3Optional C extensions, only built when the matching system library and headers were present at compile time — a classic pyenv gapInstall 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

MessageExceptionMeans
No module named 'X'ModuleNotFoundErrorThe module wasn't found on sys.path
No module named 'X.Y'; 'X' is not a packageModuleNotFoundErrorX resolved to a plain module — usually your own file
cannot import name 'Y' from 'X'ImportErrorModule loaded fine; the name inside it is missing
module 'X' has no attribute 'Y'AttributeErrorImport 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

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

More Python errors

Browse the full reference — exact message, cause, and fix — or paste a traceback and let the analyzer find the match.

All Error References Error Log Analyzer HTTP Status Codes
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.