使用-m选项或不使用-m选项执行Python代码

197
Python解释器有一个-m模块选项,可以将库模块作为脚本运行。下面是a.py的Python代码:
if __name__ == "__main__":
    print __package__
    print __name__

我测试了python -m a,得到了结果。

"" <-- Empty String
__main__

python a.py 返回:

None <-- None
__main__

对我来说,这两个调用似乎是相同的,除了在使用-m选项调用时__package__不为None。

有趣的是,对于python -m runpy a,当Python模块编译为a.pyc时,我得到的结果与python -m a相同。

这些调用之间有什么实际的区别?它们之间有什么优缺点吗?

此外,David Beazley的《Python Essential Reference》将其解释为“-m选项作为脚本运行库模块,在主脚本执行之前在__main__模块内执行”。这是什么意思?

3个回答

291
当你使用-m命令行标志时,Python会为您导入一个模块或包,然后将其作为脚本运行。当您不使用-m标志时,所命名的文件将被视为只是一个脚本而运行。
当您尝试运行一个包时,这种区别非常重要。以下两者之间有很大的区别:
python foo/bar/baz.py

并且

python -m foo.bar.baz

就像后一种情况一样,foo.bar被引入,相对导入将正确地以foo.bar作为起点工作。
演示:
$ mkdir -p test/foo/bar
$ touch test/foo/__init__.py
$ touch test/foo/bar/__init__.py
$ cat << EOF > test/foo/bar/baz.py 
> if __name__ == "__main__":
>     print __package__
>     print __name__
> 
> EOF
$ PYTHONPATH=test python test/foo/bar/baz.py 
None
__main__
$ PYTHONPATH=test python -m foo.bar.baz 
foo.bar
__main__

因此,当使用-m开关时,Python实际上必须关心包。普通脚本永远不能成为一个包,所以__package__被设置为None
但是在包内运行包或模块时,就有至少一种可能性成为一个包,所以__package__变量被设置为字符串值;在上面的示例中,它被设置为'foo.bar',对于不在包内的纯模块,它被设置为空字符串。
至于__main__模块,Python导入正在运行的脚本,就像导入常规模块一样。创建一个新的模块对象来保存全局命名空间,并存储在sys.modules['__main__']中。这就是__name__变量所指的,它是该结构中的一个键。
对于包,您可以在其中创建一个__main__.py模块,并在运行python -m package_name时运行该模块;实际上,这是唯一的一种以脚本方式运行包的方法。
$ PYTHONPATH=test python -m foo.bar
python: No module named foo.bar.__main__; 'foo.bar' is a package and cannot be directly executed
$ cp test/foo/bar/baz.py test/foo/bar/__main__.py
$ PYTHONPATH=test python -m foo.bar
foo.bar
__main__

因此,在为-m运行命名包时,Python会查找该包中包含的__main__模块并将其作为脚本执行。然后,它的名称仍设置为'__main__',模块对象仍存储在sys.modules['__main__']中。

3
命令 PYTHONPATH=test python -m foo.bar 的实际含义是什么?您能详细解释一下吗? - Andriy
6
PYTHONPATH设置一个环境变量,扩展了Python导入模块时查找的一系列目录;这里添加了test目录到该系列中。将其放在同一命令行上,它仅适用于该单个python命令。-m告诉Python要导入特定的模块,就像运行import foo.bar一样。但是,在使用该开关时,当你使用了一个包内的__main__模块作为脚本时,Python会自动运行它。 - Martijn Pieters
2
不得不始终使用“-m”并不那么用户友好。我认为混合使用和不使用“-m”会更加不方便用户。 - Cloud
1
@SiminJie:脚本可以在任何任意路径中打开,然后它们的父目录将添加到模块搜索路径中。 -m 仅适用于当前目录或已注册到搜索路径的目录。那就是我的观点。-m 不是您要为最终用户提供的东西,因为存在这个可用性问题。 - Martijn Pieters
1
@flow2k: 我的意思是 from Photos import ... 会报错。import Photos.<something> 也一样。只有 import Photos 能够正常工作,因为 Python 支持命名空间包(即两个独立的分发提供了 Photos.fooPhotos.bar 分别管理)。 - Martijn Pieters
显示剩余15条评论

53

使用 -m 选项执行 Python 代码或不执行

使用 -m 标志。

如果你要将包中的子包或模块作为程序的主入口点运行,那么使用脚本时结果几乎相同,但是在开发包时,如果没有 -m 标志,则无法使导入工作正确(相信我,我已经尝试过很多次)。

文档

-m 标志文档中所说:

在 sys.path 中搜索指定的模块并执行其内容作为 __main__ 模块。

以及

与 -c 选项一样,当前目录将添加到 sys.path 的开头。

因此,

python -m pdb

大致相当于

python /usr/lib/python3.5/pdb.py

(假设您当前目录中没有名为pdb.py的包或脚本)

解释:

行为意图 "有意与" 脚本相似。

许多标准库模块包含在其执行时作为脚本调用的代码。例如timeit 模块:

一些 Python 代码旨在作为模块运行: (我认为这个例子比命令行选项文档的例子更好)

$ python -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 3: 40.3 usec per loop
$ python -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 3: 33.4 usec per loop
$ python -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 3: 25.2 usec per loop

从Python 2.4版本的发布说明中可以看到:

命令行选项"-m",即python -m modulename将在标准库中查找模块并调用它。例如,python -m pdb 等价于 python /usr/lib/python2.4/pdb.py

后续问题

此外,David Beazley的《Python Essential Reference》解释道:" -m选项运行库模块作为脚本,在主脚本执行前在__main__模块内部执行".

这意味着您可以使用import语句查找任何模块,并以程序入口的形式运行它——如果它有一段代码块,通常位于结尾处,其中带有if __name__ == '__main__':.

-m 在不将当前目录添加到路径的情况下:

这里的其他评论说:

-m选项还会将当前目录添加到sys.path中,这显然是一种安全问题(参见:preload攻击)。 这个行为类似于Windows中的库搜索顺序(在最近已经被加固之前)。 遗憾的是,Python没有跟随这个趋势,并没有提供一种简单的方法来禁用在sys.path中添加“。”

好吧,这证明了可能存在的问题 - (在Windows中删除引号):

echo "import sys; print(sys.version)" > pdb.py

python -m pdb
3.5.2 |Anaconda 4.1.1 (64-bit)| (default, Jul  5 2016, 11:41:13) [MSC v.1900 64 bit (AMD64)]

在生产环境中使用 -I 标志来锁定此功能(自 3.4 版本起新增):

python -Im pdb
usage: pdb.py [-c command] ... pyfile [arg] ...
etc...

来自文档:

-I

以隔离模式运行Python。这也意味着使用了-E和-s选项。在隔离模式下,sys.path既不包含脚本目录,也不包含用户的site-packages目录。所有PYTHON*环境变量也会被忽略。还可能实施其他限制以防止用户注入恶意代码。

__package__是什么作用?

它启用显式相对导入,与此问题无关 - 参见这里的答案:Python中“__package__”属性的目的是什么?


当使用-m开关时,哪个路径会被添加到sys.path中? - variable
我已经引用过了,“与-c选项一样,当前目录将被添加到sys.path的开头。”但是我已经澄清了这个引用所指的内容。 - Russia Must Remove Putin
我的意思是 - 假设在 D:\test 目录中,我运行命令 - python -m foo.bar.boo,那么这会将 Python 安装文件夹或 D:\test 目录添加到 sys.path 中吗?我的理解是它会将 d:\test 添加到 sys.path,导入 foo.bar 并运行 boo 脚本。 - variable
@variable - 是的,请尝试。 - Russia Must Remove Putin

8
使用-m选项运行模块(或包)作为脚本的主要原因是简化部署,特别是在Windows上。您可以将脚本安装在Python库中与模块通常放置的相同位置 - 而不是污染PATH或全局可执行目录(例如~/.local,用户级脚本目录在Windows中非常难找到)。
然后只需键入-m,Python就会自动找到脚本。例如,python -m pip将为执行它的Python解释器实例找到正确的pip。如果没有-m,则如果用户安装了多个Python版本,哪一个是“全局”pip?
如果用户喜欢命令行脚本的“经典”入口点,这些可以轻松地添加为PATH中的小脚本,或者pip可以在安装时使用setup.py中的entry_points参数创建这些入口点。
因此,只需检查__name__ == '__main__'并忽略其他不可靠的实现细节即可。

1
-m选项还将当前目录添加到sys.path中,这显然是一个安全问题(参见:预加载攻击)。这种行为类似于Windows中的库搜索顺序(在最近加强之前)。遗憾的是,Python没有跟随这一趋势,并没有提供一种简单的方法来禁用将“.”添加到sys.path中。 - ddbug
在Python 3.4+中,可以使用“-I”标志以隔离模式运行脚本,该模式不会将当前目录添加到sys.path。 - Niko Pasanen

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