Python解释器的-m参数
目录
前言
Python 解释器有一个参数 -m 经常使用,但是一直处于“不知其所以然”的状态,在使用虚拟环境和 pip 安装依赖包时,曾敲下过无数次包含 -m 参数的命令:
python -m venv .venv
python -m pip install requests
所以本文梳理下 -m 参数到底做了什么事情,以及在什么场景下使用该参数。
核心概念
python 的 -m 参数,后面是模块的名称,该参数告诉解释器,在 sys.path 中搜索用户指定的模块,把该模块当作脚本来执行。
与普通脚本的区别
假设现在一个脚本,路径是:/home/koril/Desktop/project/script/main.py
import sys from pprint import pprint
pprint(sys.path)
比较执行该脚本的两种方式,第一种,以普通脚本文件的方式执行:
python /home/koril/Desktop/project/script/main.py
执行结果:
['/home/koril/Desktop/project/script',
'/usr/lib/python312.zip',
'/usr/lib/python3.12',
'/usr/lib/python3.12/lib-dynload',
'/usr/local/lib/python3.12/dist-packages',
'/usr/lib/python3/dist-packages']
第二种,以模块的方式执行:
cd /home/koril/Desktop/project
python -m script.main
执行结果:
['/home/koril/Desktop/project',
'/usr/lib/python312.zip',
'/usr/lib/python3.12',
'/usr/lib/python3.12/lib-dynload',
'/usr/local/lib/python3.12/dist-packages',
'/usr/lib/python3/dist-packages']
可以看出,以脚本文件的方式运行,添加到 sys.path 的路径是 /home/koril/Desktop/project/script,也就是脚本文件的父级目录,而以模块方式执行的结果显示,添加到 sys.path 的路径是 /home/koril/Desktop/project,表示模块的父级目录(也就是当前shell的所在目录)。
所以,除了添加到 sys.path 开头的路径不同之外,二者是很相似的。
包
如果传入的参数不是模块,而是一个包名,那么 python 会尝试运行包下的
__main__.py
(如果存在的话)。
在 /home/koril/Desktop/project/script 下,编写
__main__.py
:
print('this is __main__')
执行:
cd /home/koril/Desktop/project
python3 -m script
使用场景
运行内置模块
这是最最常见的做法,Python 有很多实用的内置模块,在大部分变成场景下,这些模块都是以 import 的形式导入到我们的脚本文件中使用。
但是 -m 参数给了这个模块另外一种执行方式,直接在命令行下使用该模块。
考虑一个简单的场景,我想通过 Python 的 uuid 模块得到一个随机的 UUID 值。无论是 REPL 还是编写脚本,都需要导入 uuid 模块来使用:
import uuid
print(uuid.uuid4())
通过 uuid 的源码可以看到,uuid 提供了 if name == ' main ' 块,意味着它可以作为脚本执行,并且代码里通过 argparse 解析用户传入的参数:
koril@ThinkBook:~/Desktop/project$ python3 -m uuid -h
usage: uuid.py [-h] [-u {uuid1,uuid3,uuid4,uuid5}] [-n NAMESPACE] [-N NAME]
Generates a uuid using the selected uuid function.
options:
-h, --help show this help message and exit
-u {uuid1,uuid3,uuid4,uuid5}, --uuid {uuid1,uuid3,uuid4,uuid5}
The function to use to generate the uuid. By default uuid4 function is used.
-n NAMESPACE, --namespace NAMESPACE
The namespace is a UUID, or '@ns' where 'ns' is a well-known predefined UUID addressed by namespace name. Such
as @dns, @url, @oid, and @x500. Only required for uuid3/uuid5 functions.
-N NAME, --name NAME The name used as part of generating the uuid. Only required for uuid3/uuid5 functions.
koril@ThinkBook:~/Desktop/project$ python3 -m uuid -u uuid4
772b97d4-89a5-4802-9fab-6a2b3911f335
由于 -u 有默认值(uuid4),所以直接可以在命令行中非常简单的获取一个随机的 uuid4 值:
python -m uuid
除了 uuid 之外,http.server 也很实用,它可以快速在当前目录运行一个 http 服务器:
python -m http.server
默认绑定 0.0.0.0 的 8000 端口,可以通过参数修改。
timeit 模块可以用来测量命令的执行时间,除了这几个外,还有很多有用的命令,不再一一赘述。
pip
在一台机器上可能存在多个 Python 版本,3.7、3.8、3.11 等等,每个版本的 Python 可能都安装了一个 pip,仅仅在命令行中执行:
pip install xxx
无法知道是哪一个版本的 pip,所以推荐写法是:
python3.7 -m pip install xxx
python3.11 -m pip install xxx
这样可以明确 pip 属于哪一个版本。
包
-m 的另外一个重要用途体现在日常项目开发的包内模块测试问题。
考虑以下项目的目录结构:
my_project/
│
├── main_package/
│ ├── __init__.py
│ └── main_module.py
│
└── scripts/
├── __init__.py
└── runner.py
runner.py 中导入了 main_module.py 中的内容:
from main_package import main_module
假设 shell 处于 my_project 目录下,直接执行 python scripts/runner.py,会爆出 main_package 找不到的错误,因为 python 把 runner.py 的父级目录 my_project/scripts 添加到了 sys.path 中,而 main_package 显然不在 scripts 目录下,所以无法找到。
这时候就可以使用 -m 参数:python -m scripts.runner。
-m 参数模拟了真实的导入环境,之前甚至看到同事想要测试 runner.py,为了以脚本方式执行,绕开 ModuleNotFoundError 的问题,直接把整个 main_package 复制到了 scripts 目录下,执行成功,最后他来一句:别管歪门邪道,你就说,它成没成功吧。。。
小结
本文主要是为了针对 python -m 的知识进行查漏补缺,作为日常最常用的参数,知其然更要知其所以然。
参考
- https://docs.python.org/3.13/using/cmdline.html#cmdoption-m
- https://stackoverflow.com/questions/50821312/what-is-the-effect-of-using-python-m-pip-instead-of-just-pip
- https://blog.dailydoseofds.com/p/python-m-the-coolest-python-flag
- https://snarky.ca/why-you-should-use-python-m-pip/