什么是__main__.py?

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

243088 次浏览

__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

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

如果你的脚本是一个目录或ZIP文件,而不是一个单独的python文件,当“script”作为参数传递给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文件。

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

$ python -m yourpackage

__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

您可以从文档中得到这一点。文档表示:

__main__ -顶级脚本环境

'__main__'是顶级代码执行的作用域的名称。 模块的__name__在从standard读取时被设置为'__main__'

.输入,一个脚本,或从交互式提示 一个模块可以发现它是否在主作用域中运行 通过检查它自己的__name__,这允许一个常见的成语 当模块作为脚本或脚本运行时,有条件地执行模块中的代码

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

对于一个包,可以通过包含a来达到相同的效果 __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__而不是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

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

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

最大的区别是没有-m开关,“package"首先将目录添加到路径中(即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开关的情况下运行你的包,相对import语句将抛出一个错误,而不是无声地运行错误的模块。