尽管这个问题已被提出并回答过多次(例如,here, here, here, and here),在我看来,没有现有的答案能够完全或简洁地捕捉到-m
旗帜。因此,下文将尝试在之前的基础上进行改进。
简介(TLDR)
The -m
flag 可以做很多事情,但并不是所有的事情都是一直需要的。简而言之,它可用于: (1) 通过模块名而不是文件名从命令行执行 python 代码 (2) 添加一个目录sys.path
用于import
解析并 (3) 执行包含来自命令行的相对导入的 python 代码。
预赛
为了解释-m
标志我们首先需要解释一些术语。
Python 的主要组织单位被称为module。模块有两种类型之一:代码模块和包模块。代码模块是包含 python 可执行代码的任何文件。包模块是包含其他模块(代码模块或包模块)的任何目录。最常见的代码模块类型是*.py
文件,而最常见的包模块类型是包含__init__.py
file.
Python 允许通过两种方式唯一标识模块:模块名和文件名。一般来说,模块在 Python 代码中通过模块名来标识(例如,import <modulename>
)并在命令行上按文件名(例如,python <filename>
)。所有 Python 解释器都能够通过遵循相同的几个明确定义的规则将模块名转换为文件名。这些规则取决于sys.path
多变的。通过更改此变量,可以更改 Python 将模块名解析为文件名的方式(有关如何完成此操作的更多信息,请参阅PEP 302).
所有模块(代码和包)都可以执行(即与模块关联的代码将由 Python 解释器评估)。根据执行方法(和模块类型),评估哪些代码以及何时评估,可能会发生很大的变化。例如,如果通过以下方式执行包模块python <filename>
then <filename>/__main__.py
将被执行。另一方面,如果通过执行相同的包模块import <modulename>
那么只有包的__init__.py
将被执行。
历史发展-m
The -m
标志首次引入于Python 2.4.1。最初它的唯一目的是提供一种识别要从命令行执行的 python 模块的替代方法。也就是说,如果我们知道<filename>
and <modulename>
对于模块,以下两个命令是等效的:python <filename> <args>
and python -m <modulename> <args>
。根据该迭代的一个约束PEP 338, 那是-m
仅适用于顶级模块名称(即可以直接在sys.path
没有任何介入的包模块)。
随着完成PEP 338 the -m
功能已扩展为支持<modulename>
超越最高层的表述。这意味着诸如http.server
现在得到了全力支持。此扩展还意味着 modulename 中的每个父包现在都已评估(即,所有父包__init__.py
除了模块名称本身引用的模块之外,还评估了文件)。
最终的主要功能增强为-m
带着PEP 366。通过这次升级-m
在执行模块时不仅支持绝对导入,还支持显式相对导入。这是通过改变实现的-m
这样它就设置了__package__
变量给定模块名的父模块(除了它已经做的所有其他事情之外)。
用例
有两个值得注意的用例-m
flag:
-
从命令行执行可能不知道其文件名的模块。该用例利用了 Python 解释器知道如何将模块名转换为文件名的事实。当想要从命令行运行 stdlib 模块或第 3 方模块时,这是特别有利的。例如,很少有人知道该文件的文件名http.server
模块,但大多数人确实知道它的模块名称,因此我们可以使用以下命令从命令行执行它python -m http.server
.
-
执行包含绝对或相对导入的本地包,而无需安装它。该用例的详细信息参见PEP 338并利用当前工作目录被添加到的事实sys.path
而不是模块的目录。这个用例与使用非常相似pip install -e .
在开发/编辑模式下安装包。
缺点
随着所有的增强-m
多年来它仍然有一个主要缺点——它只能执行用 Python 编写的模块(即*.py
)。例如,如果-m
用于执行C编译的代码模块将产生以下错误,No code object available for <modulename>
(see here更多细节)。
详细比较
通过 import 语句执行模块(即import <modulename>
):
-
sys.path
is not以任何方式修改
-
__name__
被设置为绝对形式<modulename>
-
__package__
设置为直接父包<modulename>
-
__init__.py
针对所有包进行评估(包括其自己的包模块)
-
__main__.py
is not评估包模块;代码针对代码模块进行评估
通过带有文件名的命令行执行模块(即python <filename>
):
-
sys.path
修改为包含最终目录<filename>
-
__name__
被设定为'__main__'
-
__package__
被设定为None
-
__init__.py
不评估任何包(包括它自己的包模块)
-
__main__.py
评估包模块;代码针对代码模块进行评估。
通过带有模块名称的命令行执行模块(即python -m <modulename>
):
-
sys.path
修改为包含当前目录
-
__name__
被设定为'__main__'
-
__package__
设置为直接父包<modulename>
-
__init__.py
针对所有包进行评估(包括其自己的包模块)
-
__main__.py
评估包模块;代码针对代码模块进行评估
结论
The -m
最简单的是,flag 是一种使用模块名而不是文件名从命令行执行 python 脚本的方法。真正的力量-m
然而,在于它能够结合以下力量import
语句(例如,支持显式相对导入和自动打包__init__
评估)并方便地使用命令行。