【问题标题】:What is __main__.py?什么是__main__.py?
【发布时间】:2011-05-01 20:53:43
【问题描述】:

__main__.py 文件的用途是什么,我应该在其中放入什么样的代码,什么时候应该有?

【问题讨论】:

标签: python


【解决方案1】:

通常,Python 程序通过在命令行上命名 .py 文件来运行:

$ python my_program.py

您还可以创建一个包含代码的目录或 zip 文件,并包含 __main__.py。然后你可以简单地在命令行命名目录或压缩文件,它会自动执行__main__.py

$ python my_program_dir
$ python my_program.zip
# Or, if the program is accessible as a module
$ python -m my_program

您必须自己决定您的应用程序是否可以从这样的执行中受益。


注意__main__ 模块 通常不是来自__main__.py 文件。可以,但通常不会。当您运行像python my_program.py 这样的脚本时,该脚本将作为__main__ 模块而不是my_program 模块运行。这也发生在以 python -m my_module 或其他几种方式运行的模块上。

如果您在错误消息中看到名称 __main__,这并不一定意味着您应该查找 __main__.py 文件。

【讨论】:

  • 我发现python -m program_dirpython program_dir 有点不同:后者从不在目录中运行__init__.py(如果有的话)。
  • @brk:现在看来不是这样。我刚刚尝试了python3 program_dir,它运行了__init__.py
  • @mk12 我刚刚尝试过,我可以确认@brk 的发现:python3 dir 运行__main__.py 但不是__init__.py,而python3 -m dir 运行两者。
  • @mk12 可能您在__main__.py 中有一些代码触发了__init__.py 的导入
  • 我把__main__.py放在项目的根目录下,这样开发者就可以运行python .,瞧!
【解决方案2】:

__main__.py 文件的用途是什么?

在创建 Python 模块时,通常使模块在作为程序的入口点运行时执行某些功能(通常包含在 main 函数中)。这通常通过大多数 Python 文件底部的以下常用习语来完成:

if __name__ == '__main__':
    # execute only if run as the entry point into the program
    main()

您可以使用__main__.py 获得与 Python 包相同的语义,它可能具有以下结构:

.
└── demo
    ├── __init__.py
    └── __main__.py

要查看此内容,请将以下内容粘贴到 Python 3 shell 中:

from pathlib import Path

demo = Path.cwd() / 'demo'
demo.mkdir()

(demo / '__init__.py').write_text("""
print('demo/__init__.py executed')

def main():
    print('main() executed')
""")

(demo / '__main__.py').write_text("""
print('demo/__main__.py executed')

from demo import main

main()
""")

我们可以把demo当作一个包来实际导入,它执行__init__.py中的顶层代码(但不是main函数):

>>> import demo
demo/__init__.py executed

当我们使用包作为程序的入口点时,我们执行__main__.py中的代码,它首先导入__init__.py

$ python -m demo
demo/__init__.py executed
demo/__main__.py executed
main() executed

您可以从文档中得出这一点。 documentation 说:

__main__ — 顶级脚本环境

'__main__' 是顶级代码执行的范围的名称。 从标准读取时,模块的__name__ 设置为等于'__main__' 输入、脚本或来自交互式提示。

一个模块可以发现它是否在主范围内运行 通过检查它自己的__name__,它允许一个常见的习惯用法 当模块作为脚本运行时,有条件地执行模块中的代码或 使用python -m,但在导入时没有:

if __name__ == '__main__':
     # execute only if run as a script
     main()

对于一个包,同样的效果可以通过包含一个 __main__.py 模块,其内容会在以-m 运行模块时执行。

压缩

您也可以将此目录(包括__main__.py)压缩到一个文件中,然后像这样从命令行运行它 - 但请注意,压缩包不能执行子包或子模块作为入口点:

from pathlib import Path

demo = Path.cwd() / 'demo2'
demo.mkdir()

(demo / '__init__.py').write_text("""
print('demo2/__init__.py executed')

def main():
    print('main() executed')
""")

(demo / '__main__.py').write_text("""
print('demo2/__main__.py executed')

from __init__ import main

main()
""")

注意细微的变化——我们从__init__ 导入main 而不是demo2——这个压缩目录不被视为一个包,而是一个脚本目录。所以它必须在没有-m标志的情况下使用。

与问题特别相关 - zipapp 导致压缩目录默认执行 __main__.py - 它在 __init__.py 之前首先执行:

$ python -m zipapp demo2 -o demo2zip
$ python demo2zip
demo2/__main__.py executed
demo2/__init__.py executed
main() executed

再次注意,这个压缩目录不是一个包——你也不能导入它。

【讨论】:

  • 你会在__main__.py 中使用if __name__ == '__main__' 吗?还是没有必要?
  • 我认为这是多余的。
【解决方案3】:

__main__.py 用于 zip 文件中的 python 程序。 __main__.py 文件将在 zip 文件运行时执行。例如,如果 zip 文件是这样的:

test.zip
     __main__.py

__main__.py的内容是

import sys
print "hello %s" % sys.argv[1]

如果我们要运行 python test.zip world,我们会得到 hello world

所以__main__.py 文件在对 zip 文件调用 python 时运行。

【讨论】:

    【解决方案4】:

    您在yourpackage 中创建__main__.py 以使其可执行为:

    $ python -m yourpackage
    

    【讨论】:

    • -m 仅在程序可作为模块访问时有效,否则您可以使用 python <yourpackage> 注意:没有 -m 选项
    • @BenyaminJafari 无法编写无法以a module 访问的命令行 Python 程序。也许你的意思是package
    • 当我们创建一个包含 main.py 的 Python 包时,运行它 python -m <yourproject> 不起作用,-m 是一个冗余选项,但 @987654331 @ 效果很好。
    • @BenyaminJafari -m 标志在某些情况下确实有所作为。从目录a 执行并假设脚本a/b/c/__main__.py...python -m b.c 将从目​​录a 执行,并且主脚本的导入将相对于a。但是python b/c 将从目​​录c 的导入范围执行,因此像import b.d 这样的主脚本中的任何导入都将失败。
    • @BenyaminJafari 这是一个危险的建议。在有或没有-m 开关的情况下运行之间有一个非常重要的区别。我在下面添加了一个答案来澄清这一点,供任何想知道差异的未来读者使用。
    【解决方案5】:

    这里的一些答案暗示给定一个包含__main__.py 文件的“包”目录(有或没有明确的__init__.py 文件),使用-m 开关或运行该目录没有区别没有。

    最大的区别在于没有-m开关,“包”目录先添加到路径(即sys.path),然后文件正常运行,没有包语义

    使用 -m 开关,尊重包语义(包括相对导入),并且包目录本身永远不会添加到系统路径

    这是一个非常重要的区别,无论是相对导入是否有效,但更重要的是规定在系统模块意外隐藏的情况下将导入什么


    例子:

    考虑一个名为PkgTest 的目录,其结构如下

    :~/PkgTest$ tree
    .
    ├── pkgname
    │   ├── __main__.py
    │   ├── secondtest.py
    │   └── testmodule.py
    └── testmodule.py
    

    __main__.py 文件的内容如下:

    :~/PkgTest$ cat pkgname/__main__.py
    import os
    print( "Hello from pkgname.__main__.py. I am the file", os.path.abspath( __file__ ) )
    print( "I am being accessed from", os.path.abspath( os.curdir ) )
    from  testmodule import main as firstmain;     firstmain()
    from .secondtest import main as secondmain;    secondmain()
    

    (其他文件定义类似,打印输出类似)。

    如果您在没有-m 开关的情况下运行此程序,您将得到这样的结果。请注意,相对导入失败,但更重要的是请注意选择了错误的测试模块(即相对于工作目录):

    :~/PkgTest$ python3 pkgname
    Hello from pkgname.__main__.py. I am the file ~/PkgTest/pkgname/__main__.py
    I am being accessed from ~/PkgTest
    Hello from testmodule.py. I am the file ~/PkgTest/pkgname/testmodule.py
    I am being accessed from ~/PkgTest
    Traceback (most recent call last):
      File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main
        "__main__", mod_spec)
      File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
        exec(code, run_globals)
      File "pkgname/__main__.py", line 10, in <module>
        from .secondtest import main as secondmain
    ImportError: attempted relative import with no known parent package
    

    而使用 -m 开关,你会得到你(希望)所期望的:

    :~/PkgTest$ python3 -m pkgname
    Hello from pkgname.__main__.py. I am the file ~/PkgTest/pkgname/__main__.py
    I am being accessed from ~/PkgTest
    Hello from testmodule.py. I am the file ~/PkgTest/testmodule.py
    I am being accessed from ~/PkgTest
    Hello from secondtest.py. I am the file ~/PkgTest/pkgname/secondtest.py
    I am being accessed from ~/PkgTest
    


    注意:老实说,应该避免在没有-m 的情况下运行。事实上,我会更进一步说,我会创建任何executable packages,除非通过-m 开关运行,否则它们会失败。

    换句话说,我只会通过“相对导入”显式地从“包内”模块导入,假设所有其他导入都代表系统模块。如果有人尝试在没有-m 开关的情况下运行您的包,则相对导入语句将引发错误,而不是静默运行错误的模块。

    【讨论】:

    • 解释相对导入的含义非常有帮助,谢谢。
    • 无耻插件:如果有人感兴趣,here is a nice template I made 创建这样自包含的可执行 python 包。 :)
    • 等一下,如果你想使用testmodule 来自PkgTest/pkgname 的那个,你将如何强制使用-m
    • @ShifraSec 你的意思是在代码中?您可以使用相对导入来导入它,即from . testmodule import main(注意点!)。或者,您可以明确使用包名称并说,例如from pkgname import testmodule。如果您想在代码中导入 both,可以使用不同的名称加载它们,例如import testmodule as testmodule1; from pkgname import testmodule as testmodule2.
    • @ShifraSec 不用担心。一点也不;这是你必须玩弄的那种东西,直到你能很好地掌握它。 :)
    【解决方案6】:

    如果您的脚本是目录或 ZIP 文件而不是单个 python 文件,则当“脚本”作为参数传递给 python 解释器时,__main__.py 将被执行。

    【讨论】:

      猜你喜欢
      • 2015-09-28
      • 2018-08-30
      • 2013-09-03
      • 2017-06-08
      • 2017-12-12
      • 2018-11-22
      相关资源
      最近更新 更多