-m开关的目的是什么?

348

你能解释一下调用以下两种方式的区别吗?

python -m mymod1 mymod2.py args

python mymod1.py mymod2.py args

看起来两种情况下都调用了mymod1.py并使用了sys.argv

['mymod1.py', 'mymod2.py', 'args']

那么,-m开关是用来做什么的?


请纠正我,但是-m似乎在默认库路径中搜索mymod1。例如:python -m SimpleHTTPServer可以工作,而python SimpleHTTPServer会失败并显示can't open file 'SimpleHTTPServer': [Errno 2] No such file or directory - Basj
20
我发现这里的答案更清晰:https://dev59.com/3FYO5IYBdhLWcg3wHuEA - Casebash
6个回答

224
尽管这个问题已经被问过和回答过多次(例如,这里这里这里这里),但在我看来,没有一个现有的答案完全或简洁地涵盖了-m标志的所有含义。因此,以下内容将尝试改进之前的回答。
简介(TLDR) -m标志有很多功能,并非所有功能都会一直需要。简而言之,它可以用于:(1)通过模块名而不是文件名在命令行上执行Python代码,(2)将目录添加到sys.path以供import解析使用,以及(3)在命令行上执行包含相对导入的Python代码。
准备工作
为了解释-m标志,我们首先需要解释一些术语。
Python的主要组织单位被称为模块。模块有两种类型:代码模块和包模块。代码模块是包含Python可执行代码的任何文件。包模块是包含其他模块(无论是代码模块还是包模块)的任何目录。最常见的代码模块类型是*.py文件,而最常见的包模块类型是包含__init__.py文件的目录。
Python允许以两种方式唯一标识模块:模块名和文件名。一般情况下,在Python代码中通过模块名来标识模块(例如,import <模块名>),而在命令行中通过文件名来标识模块(例如,python <文件名>)。所有的Python解释器都能够按照相同的几个明确定义的规则将模块名转换为文件名。这些规则依赖于sys.path变量。通过修改这个变量,可以改变Python将模块名解析为文件名的方式(有关如何实现这一点的更多信息,请参阅PEP 302)。
所有模块(包括代码和包)都可以被执行(即与模块相关的代码将由Python解释器评估)。根据执行方法(和模块类型),哪些代码被评估以及何时评估可能会有很大变化。例如,如果通过python <filename>执行一个包模块,那么将执行<filename>/__main__.py。另一方面,如果通过import <modulename>执行同一个包模块,那么只会执行包的__init__.py

-m的历史发展

-m标志首次引入于Python 2.4.1。最初它的唯一目的是提供一种替代的方式来从命令行执行Python模块。也就是说,如果我们既知道模块的<filename>又知道<modulename>,那么以下两个命令是等效的:python <filename> <args>python -m <modulename> <args>。根据PEP 338的约束,这个迭代的一个限制是-m只能用于顶级模块名(即可以直接在sys.path上找到的模块,而不需要任何中间包模块)。

随着PEP 338的完成,-m功能得到了扩展,以支持顶级之外的<modulename>表示。这意味着像http.server这样的名称现在得到了完全支持。这个扩展还意味着modulename中的每个父包都会被评估(即,所有父包的__init__.py文件都会被评估),除了modulename本身引用的模块。 -m的最后一个重要功能增强是通过PEP 366实现的。通过这个升级,-m不仅可以支持绝对导入,还可以在执行模块时支持显式相对导入。这是通过改变-m来设置__package__变量为给定modulename的父模块(除了它已经做的其他一切)来实现的。

用例

-m标志有两个值得注意的用例:
  1. 从命令行执行模块,而不知道它们的文件名。这种用法利用了Python解释器知道如何将模块名转换为文件名的事实。当我们想要从命令行运行stdlib模块或第三方模块时,这是特别有优势的。例如,很少有人知道http.server模块的文件名,但大多数人知道它的模块名,所以我们可以使用python -m http.server从命令行执行它。
  2. 执行包含绝对或相对导入的本地包,而无需安装它。这种用法在PEP 338中有详细说明,利用的是当前工作目录被添加到sys.path而不是模块目录的事实。这种用法与使用pip install -e .以开发/编辑模式安装包非常相似。

缺点

尽管多年来对-m进行了许多改进,但它仍然有一个主要缺点——它只能执行用Python编写的模块(即*.py)。例如,如果使用-m来执行一个C编译的代码模块,将会产生以下错误:No code object available for <modulename>(更多详细信息请参见here)。

详细比较

通过import语句执行模块(即import <modulename>):

  • sys.path 在任何方式下都不会被修改
  • __name__ 被设置为 <modulename> 的绝对形式
  • __package__ 被设置为 <modulename> 的直接父包
  • 对所有包(包括自身的包模块)都会评估 __init__.py
  • 对包模块不会评估 __main__.py;该代码会被评估为代码模块

通过命令行使用文件名执行模块(例如,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 评估;对代码模块进行代码评估

结论

-m 标志最简单的作用是通过使用模块名而不是文件名,在命令行上执行 Python 脚本的一种方式。然而,-m 的真正威力在于它能够将 import 语句的功能(例如,支持显式相对导入和自动包 __init__ 评估)与命令行的便利性结合起来。


1
你能否在这里添加使用 python -m packagename 调用包的方法,就像这里提到的一样:https://dev59.com/Dmsz5IYBdhLWcg3w3bxb#53772635 - variable
14
这是我阅读过的关于这个主题最全面的介绍。谢谢! - Géry Ogam
3
我认为这句话需要修改:“例如,如果通过 python <filename> 执行一个包模块,则会先评估 <filename>/__init__.py,然后是 <filename>/__main__.py.
  • 你是不是想说 <dirname>?如果是这样的话,只有 <dirname>/__main__.py 会被执行。
- starriet
1
@starriet 哦,你说得对。我确信我已经测试过了,但是当我再次尝试时,它的行为就像你所说的那样。哦,看起来我最后弄对了。我想我没有回去修复它。是的,我使用<filename>通常表示任何路径(包括目录)。 - Mark Rucker
通过文件名(即python <filename>)执行模块:如果运行包模块,则__package__ 为“”。“Python foo”其中foo是一个包。 - figs_and_nuts
显示剩余3条评论

192
PEP 338的 Rationale 部分的第一行说:
Python 2.4增加了命令行开关 -m,允许使用Python模块命名空间来定位模块以便作为脚本执行。激励例子是标准库模块,如pdb和profile,并且Python 2.4 的实现对于这个有限目的很好。
因此,您可以以这种方式指定Python搜索路径中的任何模块,而不仅仅是当前目录中的文件。您是正确的,python mymod1.py mymod2.py args具有完全相同的效果。 Scope of this proposal 部分的第一行状态:
在 Python 2.4 中,使用 -m 定位到的模块将像在命令行上提供其文件名一样执行。
使用 -m 更多可能性,例如处理包中的模块等,这就是PEP 338的剩余部分所涉及的内容。阅读它以获取更多信息。

64
我最喜欢使用-m的用法是python -m SimpleHTTPServer。当我需要分享一些文件但不想使用USB闪存驱动器时,这个方法非常方便。 - arifwn
40
运行Python3需要进行一些更新,使用命令 python -m http.server 即可,这仍然很棒! - Kit Roed
19
TL;DR: 1)你可以运行 python -m package.subpackage.module 命令来使用正常的解析机制,不需要指定精确的 .py 文件路径。 2)从被运行的模块中进行相对导入是可能的,无需任何变通方法,因为它所在的包会被加载。 3)绝对导入将基于你当前的目录,而不是 .py 文件所在的目录(如果脚本位于 /path/to/my/script.py,则 sys.path 的开头为'',而不是/path/to/my)。 - clacke
2
这个答案没有明确说明的是,这仅适用于可执行模块的子集,即具有__main__.py文件的模块。大多数模块都没有这个文件,会出现错误,例如python -m sys 'print(sys.version)'失败并显示python: No code object available for sys。建议在答案中明确说明这一点。 - smci

37

值得一提的是,只有当包中含有文件__main__.py时,这才有效。否则,该包无法直接执行。

python -m some_package some_arguments

Python解释器会在包路径中查找__main__.py文件并执行。这相当于:

python path_to_package/__main__.py somearguments

执行此代码后,将会执行该内容:

if __name__ == "__main__":

2
软件包的初始化文件怎么样?如果存在主文件,init文件也会被调用吗? - variable
@变量 是的,init.py将在调用__main__.py之前被调用。 - Mark Rucker
如果您直接运行一个包的 main.py 文件,那么它将无法正常工作,因为您需要使用 python -m 标志来运行该包。 - Eliav Louski

7
我想提及的一个可能会令人困惑的情况是:假设您使用pip3安装名为foo的软件包,其中含有一个bar模块。这意味着您可以在任何目录下执行python3 -m foo.bar命令。另一方面,您有如下的目录结构:
src
|
+-- foo
    |
    +-- __init__.py
    |
    +-- bar.py

你当前位于 src/ 目录。当你运行 python -m foo.bar 时,你会运行 bar.py 文件,而不是安装的模块。然而,如果你从任何其他目录调用 python -m foo.bar,那么你将使用已安装的模块。
对于初学者来说,这种行为可能会让人困惑。这是因为 Python 搜索模块的顺序决定了这种行为,如果你使用的是 python 而不是 python -m,则不会发生这种情况。

4
简而言之,“python -m”开关的最佳用例之一是当您想要告诉Python您想运行一个模块而不是执行.py文件时。
考虑以下示例:您有一个名为“venv”的Python脚本文件(没有“.py”文件扩展名)。如果您发出此命令:
python venv

然后,Python将在当前目录中执行“venv”文件。但是,如果您想使用“python venv”模块创建新的虚拟环境,则应运行:

python -m venv

在这种情况下,Python将运行“venv”模块,而不是文件“venv”。

另一个例子,如果您想运行Pyhton的内置本地http服务器并发出命令:

python http.server

你会得到一个错误,例如:

python: can't open file '/home/user/http.server': [Errno 2] No such file or directory

那是因为Python试图执行一个名为'http.server'的文件,但没有找到它。 所以,你需要使用'-m'开关发出相同的命令:
python -m http.server

这样Python就知道你想要的是模块'http.server'而不是文件。


4

由于在谷歌上搜索“使用'python -m'”时会出现此问题,因此我想为那些喜欢模块化代码但不想创建完整的Python包或每次修改PYTHONPATHsys.path的人添加一个快速参考。

设置

让我们设置以下文件结构

.
├── f1
│   ├── f2
│   │   ├── __init__.py
│   │   └── test2.py
│   ├── __init__.py
│   └── test1.py
└── test.py

让当前路径为m1

使用python -m代替python ./*

  1. Use . qualified module names for the files (because they're being treated as modules now). For example, to run the contents in ./f1/test1.py, we do

    python -m f1.test1
    

    and not

    python ./f1/test1.py
    
  2. When using the module method, the sys.path in test1.py (when that is run) is m1. When using the ./ (relative file) method, the path is m1/f1.

    So we can access all files in m1 (and assume that it is a full python package) using -m. This is because the path to m1 is stored (as PYTHONPATH).

  3. If we want to run deeply nested "modules", we can still use . (just as we do in import statements).

    # This can be done
    python -m f1.f2.test2
    

    And in test2.py, we can do from f1.test1 import do_something without using any path gimmicks in it.

  4. Every time we do module imports this way, the __init__.py is automatically called. This is true even when we're nesting.

    python -m f1.f2.test2
    

    When we do that, the ./f1/__init__.py is called, followed by ./f1/f2/__init__.py.


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接