什么是 __main__.py?

521 投票
6 回答
294554 浏览
提问于 2025-04-16 06:14

__main__.py 文件是用来做什么的?我应该在里面放什么代码?我什么时候需要这个文件?

6 个回答

48

这里的一些回答暗示,如果有一个“包”目录(不管里面有没有明确的 __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 选项的情况下运行你的包,相对导入语句会抛出错误,而不是默默地运行错误的模块。

196

__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

再提醒一下,这个压缩目录不是一个包——你也不能导入它。

477

通常,我们在命令行中运行一个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 文件。

撰写回答