-m开关的目的是什么?

你能给我解释一下打电话和打电话的区别吗

python -m mymod1 mymod2.py args

而且

python mymod1.py mymod2.py args

似乎在这两种情况下mymod1.py被调用,而sys.argv被调用

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

那么-m开关是干什么用的呢?

147364 次浏览

PEP 338Rationale部分的第一行说:

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的其余内容。阅读它以获得更多信息。

值得一提的是这只适用于包有一个文件__main__.py否则,这个包不能直接执行。

python -m some_package some_arguments

python解释器将在要执行的包路径中查找__main__.py文件。它相当于:

python path_to_package/__main__.py somearguments

它将执行以下内容:

if __name__ == "__main__":

尽管这个问题已经被问过几次(例如,在这里在这里在这里,和在这里),但在我看来,没有现有的答案完全或简洁地抓住了-m标志的所有含义。因此,下面将尝试改进之前的内容。

介绍(TLDR)

-m标志做了很多事情,并不是所有的事情都是需要的。简而言之,它可以用于:(1)通过modulename而不是filename从命令行执行python代码(2)将一个目录添加到sys.path中以用于import解析以及(3)执行包含从命令行导入的相对python代码。

预赛

为了解释-m标志,我们首先需要解释一些术语。

Python的主要组织单位被称为模块。模块有两种类型:代码模块和包模块。代码模块是任何包含python可执行代码的文件。包模块是包含其他模块(代码模块或包模块)的目录。最常见的代码模块类型是*.py文件,而最常见的包模块类型是包含__init__.py文件的目录。

Python允许模块以两种不同的方式唯一标识:modulename和filename。通常,模块在Python代码中由modulename标识(例如import <modulename>),在命令行中由文件名标识(例如python <filename>)。所有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本身引用的模块之外,modulename中的每个父包现在都被计算(即,所有父包__init__.py文件都被计算)。

-m的最后一个主要特性增强是PEP 366。通过这次升级,-m在执行模块时不仅可以支持绝对导入,还可以支持显式相对导入。这是通过改变-m来实现的,这样它就把__package__变量设置为给定模块名的父模块(除了它已经做的所有其他事情)。

用例

-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>(详情请参阅在这里)。

详细的比较

通过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__求值)与命令行的便利性结合起来。

我只想提一个可能令人困惑的例子。

假设你使用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 -m,这种行为当然不会发生,这可能会让初学者感到困惑。原因在于Python搜索模块的顺序。

由于当你谷歌Use of "python -m"时出现了这个问题,我只是想为那些喜欢模块化代码而不喜欢每次都创建完整的python包修改 PYTHONPATHsys.path的人添加一个快速参考。

设置

让我们设置下面的文件结构

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

设当前路径为m1

使用python -m代替python ./*

  1. 为文件使用.限定的模块名称(因为它们现在被视为模块)。例如,要运行./f1/test1.py中的内容,我们需要这样做

    python -m f1.test1
    

    而不是

    python ./f1/test1.py
    
  2. 当使用模块方法时,test1.py中的sys.path(当它运行时)是m1。当使用./(相对文件)方法时,路径为m1/f1

    因此,我们可以使用-m访问所有文件 m1(并假设它是一个完整的python包)。这是因为m1的路径被存储(作为PYTHONPATH)。

  3. 如果我们想要运行深度嵌套的“模块”,我们仍然可以使用.(就像在import语句中所做的那样)。

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

    而在test2.py中,我们可以在不使用任何路径噱头的情况下执行from f1.test1 import do_something

  4. 每次以这种方式导入模块时,__init__.py都会被自动调用。即使在我们筑巢的时候也是如此。

    python -m f1.f2.test2
    

    当我们这样做时,./f1/__init__.py被调用,后面跟着./f1/f2/__init__.py