You run your command-line tool from the project folder, and it works:
cd ~/code/pricing
python -m pricing.cli report
From your home folder, the same command fails with
ModuleNotFoundError: No module named 'pricing'. No file moved. What changed
is the list of folders Python searched. Print that list with the same Python,
from the same folder as the failing command:
import os
import sys
print("python:", sys.executable)
print("folder:", os.getcwd())
for entry in sys.path:
print(repr(entry))
Python searches a list of folders in order
sys.path is the search path, the list of folders Python looks in, in
order, when you import a module. For import pricing, Python checks each
entry for a pricing package or a pricing.py file, and it uses the first
one it finds.
Most entries come with your Python install, such as the standard library and site-packages, the folder where pip puts installed packages. The first entry depends on how you started Python, as the table below shows.
Python finds a submodule differently. For import pricing.cli, it imports
pricing first. Then it looks for cli only in the package's own folders,
which it lists in pricing.__path__.
Python runs each module once
sys.modules is the module cache, the dictionary of modules already
imported. A second import pricing returns the cached module without running
the file again.
Python adds a module to the cache before it runs the module's code. So in a
circular import, where two modules import
each other, an import can return a module whose code has not finished. If a
cache entry is None, importing that name raises ModuleNotFoundError. A
test can set an entry to None to block a module on purpose.
The way you start Python sets the first entry
| Launch | First entry on sys.path |
|---|---|
python script.py |
The script's folder, with symlinks resolved |
python -m pkg.mod |
The current working directory |
python -c "...", stdin or the REPL |
An empty string, which means the current working directory |
python appdir or python app.zip |
That folder or zip file, which holds __main__.py |
python -m put the current folder first. In
the project folder, that folder holds pricing/, so the import worked. In
your home folder, no entry holds pricing. Install the package, for example
with pip install -e ., and the import works from any folder.
Python reads the empty string as "the current directory" each time it
searches, so it follows a later os.chdir(). An absolute path always means
the same folder.
The -P flag leaves out the first entry for the first three launch forms.
Then a stray file in your current folder cannot hide an installed module. It
keeps the folder or zip file in the last row, because Python needs that
entry to find __main__.py. sys.flags.safe_path is True when -P is on.
The -I flag turns on -P, and it also ignores PYTHON* environment
variables and your per-user site-packages folder.
Check your understanding
You run python -P app.zip. Is app.zip on sys.path?
Show answer
Hide answer
Yes. -P drops only the entry that Python adds on its own for a script, a
module or the current folder. Python needs the zip file on the search path to
find its __main__.py, so that entry stays.
Common mistakes
Running a file inside a package directly. python pricing/core.py runs
the file as a plain script, so from . import rates fails with
ImportError: attempted relative import with no known parent package. Run
python -m pricing.core from the folder that contains pricing/, so Python
knows the file belongs to pricing.
Fixing an import with sys.path.insert(). Editing the search path in
your code makes the import work on your machine and hides the real problem.
Install the package, or fix the command that starts the program. Keep path
edits for test helpers and tools, with a comment that says why.
Installing into a different Python. If sys.executable is not the Python
you installed the package into, pip put the package in another environment's
site-packages. Run python -m pip install ... with the same python that
runs the program.
Check your understanding
The tool works from the project folder, and pricing.__file__ points into that folder. Was the package never installed?
Show answer
Hide answer
You cannot tell from that alone. An editable install also loads the code from
your project folder. Run python -m pip show pricing to see whether this
Python has the package installed.
Going further
Python asks each finder in sys.meta_path in turn to find a module, and
the usual path-based finder is the one that searches sys.path. Other
entries on the search path come from the PYTHONPATH environment variable
and from .pth files in site-packages.
See the Python documentation on how Python builds sys.path, the import system and command-line options.