Understanding python -m script.py vs. python script.py and import
Have you ever wondered why you run python -m venv myenv
and not python venv myenv
?
Have you ever tried to run a Python file that imported another one of your local modules and encountered this error?
python .\test\first_module_tests\test1.py
Traceback (most recent call last):
File "~\project_dir\test\first_module_tests\test1.py", line 5, in <module>
from src.module_folder.module1 import this_function
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ModuleNotFoundError: No module named 'src'
These situations relate to how the Python interpreter works under the hood. In this article, we’ll explain the differences between executing Python code using import
, python script.py
, and python -m script.py
.
Modules and Packages in Python
Before diving into the differences, let’s define some terms:
- Modules : The fundamental building blocks of Python. There are two types:
- Code modules : Files containing Python code (e.g.,
example.py
).
- Package modules : Directories containing other modules, often marked with an
__init__.py
file. (Optional from Python 3.3).
- Code modules : Files containing Python code (e.g.,
- Modulename vs. Filename :
- Modulename : The Python identifier used for importing a module (e.g.,
import mymodule
).
- Filename : The actual file path used to execute a script (e.g.,
python ./mymodule.py
). Python resolves modulenames to filenames based on thesys.path
variable, which defines where the interpreter looks for modules.
- Modulename : The Python identifier used for importing a module (e.g.,
How Python Executes Modules
Let’s compare three ways of running Python code:
import
(module execution through import statement): When you useimport
, the interpreter loads the module into memory, executes its top-level code, and makes its contents available for use. For example:
import mymodule
- Effects :
sys.path
is not modified .__name__
is set to the absolute modulename (e.g.,mymodule
).For package modules, the
__init__.py
file is executed.
python script.py
(module execution through command line with filename). Executing a file withpython script.py
runs it as a standalone script:
python myscript.py
- Effects :
The directory of the script (
myscript.py
) is added tosys.path
.__name__
is set to__main__
.For package modules,
__init__.py
is not executed.
python -m modulename
(module exeuction through command line with modulename). The-m
flag allows you to execute a module by its name:
python -m mypackage.myscript
- Effects :
The current working directory (i.e., from where you are executing your commands) is added to
sys.path
.__name__
is set to__main__
.For package modules,
__init__.py
is executed, followed by__main__.py
.
Key Differences
Execution Method | sys.path | __name__ |
Executes __init__.py |
Executes __main__.py |
---|---|---|---|---|
import modulename |
Unchanged | modulename | Yes | No |
python script.py |
Adds script’s directory | __main__ |
No | Yes (for package module) |
python -m modulename |
Adds current directory | __main__ |
Yes | Yes (for package module) |
Why Use python -m
?
The -m
flag combines the convenience of modulenames with the ability to do relative imports from a root directory. Particularly:
- Executing Standard Library or Third-Party Tools : Many tools (like
venv
orhttp.server
) are modules in the Python standard library or installed packages. Their filenames are not found easily, but their modulenames are:
python -m venv
That solves our first question from the intro.
If we tried to run python venv
, we would be referencing the filename. Hence, Python would try to look for venv in the current directory, which will result in:
python venv
.\venv': [Errno 2] No such file or directory
- Running Local Modules with Imports :
python -m
helps avoid import errors when running modules from a root project directory. Imagine you’re working with this directory structure:
project_dir/
src/
module1.py
tests/
test_module1.py
Running python -m tests.test_module1
ensures the src
package in test_module1.py
is available for imports.
Please note several caveats here.
First, for testing you would usually resort to either pytest ...
or python -m unittest tests/test_module1.py
. Second, one common error that I got when I didn’t know about the differences between modulenames and filenames was trying to run python -m tests/test_module1.py
, which returned the following message: > Relative module names not supported
At first, I thought this was related to the imports in test_module1.py. However, now I realize this is due to us using the -m flag for modulenames, which means the Python interpreter is treating the filepath as a modulename, and warns you that relative module names with slashes are not supported.
On the other hand, if you try to use the syntax for filenames python tests/test_module1.py
, this will fail when there is an import in your tests that requires to use the src structure.
Why? Because as mentioned above, the filename execution from the command line (python script.py) adds the directory of the executed file to the sys.path, whereas the modulename execution adds the current working directory.
Therefore, if we try to import src without having added the current working directory (project directory) to the sys.path, the Python interpreter won’t find any module named src in the script directory (in this case, tests
), telling us src wasn’t found.
Conclusion
The python -m
flag is more than a shorthand—it’s a powerful tool that bridges the gap between imports and script execution. It allows you to execute modules while preserving the integrity of their package structure, supports relative imports, and is essential for running tools and packages from the command line.
On the other hand, executing python without the -m flag executes modules using their filenames, adding their directory to sys.path.
I hope you found this blog post useful! If you have any questions or comments, feel free to reach out.
Note: The main source for this post is this stackoverflow response to a similar question.