是否使用 -m 选项执行 Python 代码

Python 解释器有 -m 模组选项,该选项“将库模块 模组作为脚本运行”。

使用这个 python 代码 a.py:

if __name__ == "__main__":
print __package__
print __name__

我测试了 python -m a

"" <-- Empty String
__main__

python a.py又回来了

None <-- None
__main__

对我来说,这两个调用似乎是相同的,除了 _ _ package _ _ is not Nothing 在使用-m 选项调用时。

有趣的是,使用 python -m runpy a,我得到的结果与使用 python 模块编译得到 a.pyc 的 python -m a相同。

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

此外,DavidBeazley 的 Python 基本参考文献将其解释为“ M 选项将库模块作为脚本运行,该脚本在 _ _ main _ _ 模块中执行,然后再执行主脚本”,这是什么意思?

139323 次浏览

是否使用 -m 选项执行 Python 代码

使用 -m标志。

当你有一个脚本的时候,结果几乎是一样的,但是当你开发一个没有 -m标志的包的时候,如果你想在包中运行一个子包或者模块作为你的程序的主要入口点(相信我,我已经试过了) ,那么就没有办法让导入正确工作

医生

就像 - M 旗上的文件说的:

搜索命名模块的 sys.path,并将其内容作为 __main__模块执行。

还有

与-c 选项一样,工作目录将添加到 sys.path 的开始部分。

所以

python -m pdb

大致相当于

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

(假设您的工作目录中没有一个名为 pdb.py 的包或脚本)

说明:

行为被“故意地类似”脚本。

许多标准库模块包含在执行时作为脚本调用的代码

有些 python 代码是 作为模块运行:(我认为这个示例比命令行选项 doc 示例更好)

$ 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 基本参考文献将其解释为“ The - m 选项以脚本的形式运行库模块,该脚本在执行主脚本之前在 __main__模块内执行。”。

这意味着您可以使用 import 语句查找的任何模块都可以作为程序的入口点运行——如果它有一个代码块,通常在接近结尾的地方,带有 if __name__ == '__main__':

返回文章页面工作目录 -m:

其他地方的评论说:

M 选项还将工作目录添加到 sys.path,这显然是一个安全问题(参见: preload 攻击)。这种行为类似于 Windows 中的库搜索顺序(在最近强化之前)。可惜的是,Python 没有跟上潮流,也没有提供禁用添加的简单方法。到 sys.path

好吧,这说明了一个可能的问题-(在窗口中删除引号) :

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-package 目录。所有 PYTHON * 环境变量也被忽略。为防止用户注入恶意代码,可能会施加进一步的限制。

__package__是做什么的?

它支持显式的相对导入,但与这个问题没有特别密切的关系——看这里的答案: Python 中“ _ _ package _ _”属性的用途是什么?

当您使用 -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__

因此,Python 在使用 -m开关时实际上必须关心包。一个正常的脚本永远不能 一个包,所以 __package__被设置为 None

但是用 -m运行一个包或模块 在里面一个包,现在至少有一个包的 可能性,所以 __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__']中。

使用-m 脚本运行模块(或包)的主要原因是为了简化部署,特别是在 Windows 上。您可以将脚本安装在 Python 库中模块通常所在的位置——而不是污染 PATH 或全局可执行目录(如 ~/)。Local (在 Windows 中很难找到 per-user script 目录)。

然后输入-m,Python 自动找到脚本。例如,python -m pip将为执行它的同一个 Python 解释器实例找到正确的 pip。如果没有 -m,如果用户安装了几个 Python 版本,那么哪一个是“ global”pip?

如果用户喜欢命令行脚本的“经典”入口点,那么可以在 PATH 中的某个地方轻松地将这些入口点作为小脚本添加,或者 pip 可以在安装时使用 setup.py 中的 entry _ points 参数创建这些入口点。

因此,只需检查 __name__ == '__main__'并忽略其他不可靠的实现细节。