2023-09-05 08:00:00

什么是__main__.py?

__main__.py文件是干什么用的,我应该把什么样的代码放进去,什么时候我应该有一个?


当前回答

你在你的包中创建__main__.py,使其可执行:

$ python -m yourpackage

其他回答

你在你的包中创建__main__.py,使其可执行:

$ python -m yourpackage

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

创建Python模块时,通常会让模块在作为程序入口点运行时执行某些功能(通常包含在主函数中)。这通常是在大多数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中的顶层代码(但不是主函数):

>>> import demo
demo/__init__.py executed

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

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

您可以从文档中得到这一点。文件说:

__main__ — Top-level script environment '__main__' is the name of the scope in which top-level code executes. A module’s __name__ is set equal to '__main__' when read from standard input, a script, or from an interactive prompt. A module can discover whether or not it is running in the main scope by checking its own __name__, which allows a common idiom for conditionally executing code in a module when it is run as a script or with python -m but not when it is imported: if __name__ == '__main__': # execute only if run as a script main() For a package, the same effect can be achieved by including a __main__.py module, the contents of which will be executed when the module is run with -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__而不是demo2导入main——这个压缩目录不被视为一个包,而是一个脚本目录。所以它必须在不带-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文件的“包”目录(有或没有显式的__init__.py文件),在运行该目录时使用-m开关或不使用-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开关运行该程序,将得到这样的结果。请注意,相对导入失败,但更重要的是,请注意选择了错误的testmodule(即相对于工作目录):

:~/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的情况下运行。事实上,我想进一步说,我将以这种方式创建任何可执行包,除非通过-m开关运行,否则它们将失败。

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

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

test.zip
     __main__.py

而__main__.py的内容为

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

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

因此,在zip文件上调用python时,__main__.py文件将运行。

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

$ python my_program.py

你也可以创建一个充满代码的目录或zip文件,并包含__main__.py。然后你可以简单地在命令行上命名目录或zip文件,它会自动执行__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文件。