【问题标题】:Execution of Python code with -m option or not是否使用 -m 选项执行 Python 代码
【发布时间】:2014-04-10 02:07:30
【问题描述】:

python 解释器有 -m module 选项“将库模块 module 作为脚本运行”。

用这个python代码a.py:

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

我测试了python -m a 得到

"" <-- Empty String
__main__

python a.py 返回

None <-- None
__main__

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

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

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

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

【问题讨论】:

    标签: python module package


    【解决方案1】:

    当您使用-m command-line flag 时,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

    但是运行一个包或模块inside一个带有-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__']中。

    【讨论】:

    • 命令PYTHONPATH=test python -m foo.bar 究竟是什么意思?请您详细解释一下好吗?
    • @Andriy: PYTHONPATH 设置环境变量;它扩展了 Python 在导入时将在其中查找模块的目录系列;在这里它将test 目录添加到该系列。通过将它放在同一命令行上,它only 应用于单个python 命令。 -m 告诉 Python 导入特定模块,就像你运行 import foo.bar 一样。但是,当您使用该开关时,Python 会自动将包内的 __main__ 模块作为脚本运行。
    • having to use -m always is not that user-.friendly. 我认为混合使用和不使用-m 对用户不太友好。
    • @SiminJie:脚本可以在任意路径中打开,然后将其父目录添加到模块搜索路径中。 -m 仅适用于当前目录或已在搜索路径上注册的目录。那是我的观点。 -m 不是你为了那个可用性问题而提供给最终用户的东西。
    • @flow2k:我的意思是from Photos import ... 会抱怨。 import Photos.&lt;something&gt; 也是如此。 import Photos 之所以有效,是因为 Python 支持命名空间包(其中两个独立的发行版分别提供 Photos.fooPhotos.bar,它们可以独立管理)。
    【解决方案2】:

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

    使用-m 标志。

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

    文档

    喜欢docs on the -m flag 说:

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

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

    所以

    python -m pdb
    

    大致相当于

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

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

    解释:

    行为“故意类似于”脚本。

    许多标准库模块包含在执行时作为脚本调用的代码。一个例子是timeit module:

    一些python代码打算是run as a module:(我觉得这个例子比命令行选项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
    

    And from the release note highlights for Python 2.4:

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

    后续问题

    此外,David Beazley 的 Python Essential Reference 将其解释为“The -m 选项将库模块作为脚本运行,该脚本在执行主脚本之前在 __main__ 模块内执行”。

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

    -m不添加当前目录到路径:

    其他地方的评论说:

    -m 选项也将当前目录添加到 sys.path 中,这显然是一个安全问题(参见:预加载攻击)。这种行为类似于 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...
    

    来自the docs

    -I

    以隔离模式运行 Python。这也意味着 -E 和 -s。在隔离模式下,sys.path 既不包含脚本的目录,也不包含用户的站点包目录。所有 PYTHON* 环境变量也会被忽略。可能会施加更多限制以防止用户注入恶意代码。

    __package__ 是做什么的?

    它启用了显式的相对导入,但与这个问题并不特别相关 - 请在此处查看此答案:What's the purpose of the "__package__" attribute in Python?

    【讨论】:

    • 使用-m开关时sys.path中添加了哪个路径?
    • 我已经引用了那个,“与 -c 选项一样,当前目录将添加到 sys.path 的开头。”但我已经澄清了引用的含义。
    • 我的意思是 - 假设在 D:\test 目录中,我运行命令 - python -m foo.bar.boo 那么这会将 python 安装文件夹或 D:\test 目录添加到 sys 。小路?我的理解是它会将 d:\test 添加到 sys.path,导入 foo.bar 并运行 boo 脚本
    • @variable - 是的,试试看。
    【解决方案3】:

    使用 -m 将模块(或包)作为脚本运行的主要原因是为了简化部署,尤其是在 Windows 上。您可以在 Python 库中模块通常所在的相同位置安装脚本 - 而不是污染 PATH 或全局可执行目录,例如 ~/.local(在 Windows 中很难找到每个用户的脚本目录)。

    然后你只需输入 -m 并且 Python 会自动找到脚本。例如,python -m pip 将为执行它的 Python 解释器的同一实例找到正确的 pip。如果没有 -m,如果用户安装了多个 Python 版本,哪一个是“全局”pip?

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

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

    【讨论】:

    • -m 选项也将当前目录添加到 sys.path 中,显然是一个安全问题(参见:preload attack)。这种行为类似于 Windows 中的库搜索顺序(在它最近被强化之前)。遗憾的是 Python 没有跟上潮流,也没有提供简单的方法来禁用添加 .到 sys.path。
    猜你喜欢
    • 1970-01-01
    • 2022-06-30
    • 2012-01-11
    • 1970-01-01
    • 2019-02-25
    • 1970-01-01
    • 2019-02-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多