什么是 __main__.py?
__main__.py
文件是用来做什么的?我应该在里面放什么代码?我什么时候需要这个文件?
6 个回答
这里的一些回答暗示,如果有一个“包”目录(不管里面有没有明确的 __init__.py
文件),并且里面有一个 __main__.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
选项运行,结果会是这样。注意,相对导入失败了,但更重要的是注意到选择了错误的 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
选项的情况下运行你的包,相对导入语句会抛出错误,而不是默默地运行错误的模块。
__main__.py
文件有什么用?
在创建Python模块时,通常会让这个模块在作为程序的入口点运行时执行一些功能(通常是在一个叫main
的函数里)。这通常是通过在大多数Python文件的底部放置以下常见的写法来实现的:
if __name__ == '__main__':
# execute only if run as the entry point into the program
main()
对于Python包,你可以用__main__.py
来实现同样的效果,它可能有以下结构:
.
└── demo
├── __init__.py
└── __main__.py
要查看这个效果,可以把下面的内容粘贴到Python 3的命令行中:
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__
会被设置为'__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
再提醒一下,这个压缩目录不是一个包——你也不能导入它。
通常,我们在命令行中运行一个Python程序时,只需要输入一个以.py结尾的文件名:
$ python my_program.py
你也可以创建一个包含代码的文件夹或者压缩文件,并在里面放一个叫做 __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
文件。