Debugging scene: You run a small script, get ModuleNotFoundError, stare at a package you installed yesterday and wonder: did Python lose the package, or did you? This article traces one import from the error back to the moment Python turns a name into a live module object so you can find and fix the real problem.
Short answer up front
When you write import mypkg.mod, Python resolves that name by checking (1) its cache of already loaded modules (sys.modules), (2) if it’s part of a package, by locating the package directory and its files, and (3) by searching each entry on sys.path with the import machinery described by the Python core. On first import Python executes the module’s code and stores the module object in sys.modules. Investigating failures means checking the importing package, file names, sys.path, and the import cache in that order.
Trace: one import, step by step
Imagine this project layout in your workspace:
<project-root>/
app.py
mypkg/
__init__.py
utils.py
data_reader.py
app.py contains:
import mypkg.data_reader
mypkg.data_reader.read('data.csv')
Here’s what Python does when it sees import mypkg.data_reader:
- Interpret the import name: a dotted name
mypkg.data_readermeans: find a packagemypkgthen a submoduledata_reader. - Check
sys.modules. Ifmypkgis already loaded, use that package object as the parent namespace. - Search for a package called
mypkgon each path entry insys.path. Each entry is probed by import finders/loaders (file system, zip importer, installed packages, etc.). See the official import system reference for the full machinery. - Once Python locates
mypkg(a directory on disk containing__init__.py, or a namespace package), it looks inside the package for a module file nameddata_reader.pyor a subpackage nameddata_reader. - When Python finds the module file, it compiles and executes it once, creates a module object, inserts it into
sys.modules, and binds the attribute on the parent package so thatmypkg.data_readeris available.
Process map: where to look and why
| Step | Action Python takes | What you should inspect |
|---|---|---|
| 1 | Check sys.modules cache | Run import sys; 'mypkg' in sys.modules, and inspect existing module objects. |
| 2 | Interpret dotted name and resolve parent package | Confirm package directory exists and contains __init__.py (or is a valid namespace package). |
| 3 | Walk sys.path with finders/loaders | Print sys.path and check entries (virtualenv/site-packages). See whether your project root is first. |
| 4 | Load module source, execute, cache in sys.modules | Look for runtime errors in module-level code; temporary files; stale .pyc files. |
| 5 | Bind attribute on parent package | Check parent package namespace (e.g., mypkg.__all__, dynamic imports in __init__.py). |
Short demonstrations you can run
Print the current search path and cache to inspect resolution state:
import sys
import pprint
pprint.pprint(sys.path)
print('\n-- modules loaded --')
print('mypkg' in sys.modules)
If a module is shadowing a standard library module (for example, you have a file named json.py in your project), you’ll see it by scanning sys.path entries for the conflicting filename.
To force Python to search fresh file locations (useful after creating files during runtime):
import importlib
importlib.invalidate_caches()
importlib.import_module('mypkg.data_reader')
Practical boundaries and important details
Packages: Historically, a package was a directory with an __init__.py. Modern Python supports namespace packages (no __init__.py) but these behave differently when multiple directories provide the same package name. See the packaging guide for best practices about distributing packages.
First import executes module code. That means any top-level statements run at import time (file I/O, network calls). If an import fails with an exception raised while executing the module, Python reports the exception rather than ModuleNotFoundError. To diagnose, try importing the module interactively and observe traceback.
Search path origin: sys.path starts with the directory of the script being executed, then standard library paths and site-packages. Virtual environments change the active site-packages location; that’s why using a correct environment is a common fix. See how virtual environments affect your interpreter in the guide to setting up your coding environment and the deep dive on Python virtual environments.
Troubleshooting checklist — low risk to high risk
Run these checks in order, stopping when the import begins to work or the issue is explained.
- Typo and capitalization (low risk): Python identifiers are case-sensitive. Confirm the module name matches the filename exactly.
- Current directory (low risk): Ensure you’re running your script from the expected project root or add the project directory to
sys.pathtemporarily for debugging. - Print
sys.path(low risk): Runpython -c "import sys; print('\n'.join(sys.path))"to see where Python will look. - Check module cache (low-to-medium): If an old module object is interfering, inspect
sys.modules. Useimportlib.reload()to reload a module in development. - Conflicting filenames (medium): Look for files in your project that shadow standard library names (e.g.,
socket.py,json.py). - Missing
__init__.pyvs namespace packages (medium): If a package expects__init__.pybut you removed it accidentally, add it back. If you’re relying on namespace packages across multiple locations, review packaging choices in the packaging guide. - Environment mismatch (medium-to-high): Confirm your virtual environment is activated. See how virtual environments change interpreter paths in our setup guide. A common error is installing a package into the system interpreter but running a script in a virtualenv (or vice versa).
- Installation problems (high): If the module is third-party, try
pip show pkgnameandpython -m pip install --upgrade pkgname. If the package uses native extensions, ensure the wheel is compatible with your Python version and OS. - Circular imports and runtime error in module (high): If two modules import each other at top level, one import may see a partially initialized module. Refactor to avoid top-level import cycles or move imports inside functions.
- Stale bytecode or cache directories (high): Remove
__pycache__or stale.pycfiles if you suspect inconsistent bytecode. For complex cases, create a clean virtualenv and reinstall dependencies.
Examples of common pitfalls
1) Shadowing the standard library
# Bad: project contains json.py at project root
import json # imports your file, not the stdlib json
2) Circular imports
# a.py
from b import do_b
def do_a():
pass
# b.py
from a import do_a # on import, a tries to import b and vice versa
def do_b():
pass
To fix the circular import, postpone imports into function bodies or combine modules logically. The Python import system allows partially-initialized modules in sys.modules, which is why circular imports sometimes produce confusing attribute errors.
Useful checks and commands to run now
- Show where a package was loaded from:
python -c "import pkg; print(pkg.__file__)" - Show the full module cache entry:
python -c "import sys; import pprint; pprint.pprint(sys.modules.get('mypkg'))" - Force a fresh import:
import importlib; importlib.reload(mypkg)
Helpful links inside Vandutz Academy
If you’re still getting environment-related errors, review our guide on how to set up your coding environment and the article on Python virtual environments that explains how virtualenv changes sys.path. If the module reads or writes files during import, check safe file-handling patterns in our article about how to read and write files in Python. For object and module design decisions, our piece on Python classes and OOP can help you refactor to avoid circular imports.
When dynamic importing helps
Sometimes you need to import a module path computed at runtime. Use importlib.import_module() rather than __import__() for clarity:
import importlib
module_name = 'mypkg.data_reader'
mod = importlib.import_module(module_name)
mod.read('data.csv')
This is useful in plugin systems or when scanning a directory for modules. Be careful: dynamic import still obeys the same sys.path rules and executes the module on first import.
When things still fail: recreate the environment
If you suspect complex state (stale caches, broken installs, mismatched interpreters), create a new virtual environment, install only the packages you need, and run the script there. Our setup guide covers creating clean environments and using pip properly.
Practical edge cases and packaging pointers
When you distribute code, rely on canonical packaging tools. The Python Packaging User Guide explains how to layout packages so consumers won’t encounter surprising import behavior (namespace packages, entry points, and installation locations). If you distribute code that should be importable from multiple locations, follow packaging best practices to avoid implicit namespace conflicts.
When to worry about security
Imports execute code. Don’t import untrusted packages or files. Use virtual environments and pinned dependency versions; audit third-party packages before adding them to your environment. For teams, integrate secure development checks and dependency scanning into CI — small import mistakes can have outsized security implications.
Questions people commonly ask (and concise answers)
Common import questions that help debugging
- Why does Python import the wrong module?
- Because a file on
sys.pathshadows the one you expect. Printsys.pathand search for the filename that matches the module you tried to import. - How do I reload a module while developing?
- Use
importlib.reload(module)to re-execute the module in-place. For fresh discovery after creating new files, runimportlib.invalidate_caches()before importing. - What’s the difference between a package with
__init__.pyand a namespace package? - A package with
__init__.pyis an explicit package and lives in one directory. A namespace package allows multiple directories to contribute the same package name; follow the packaging guide for when to use each approach. - How can I make Python find modules in my project folder?
- Run your script with the project root as the working directory (so it appears first in
sys.path), activate the virtual environment that has the package installed, or add a path temporarily withsys.path.insert(0, '/path/to/project')for debugging.
Sources used for this explanation
This explanation follows the documented Python import behavior and packaging guidance. The authoritative references used while researching this article are:
- Python Tutorial — Modules
- Python Language Reference — The import system
- Python documentation — The initialization of the sys.path module search path
- Python Packaging User Guide

Alex Carter is the editorial name behind Vandutz Academy, a programming blog for beginners. Alex reviews and tests the examples and explanations published on the site, with a focus on making Python, JavaScript, web development, and developer tools easier to understand.