问题题目:什么是__main__.py

摘要:

通常情况下,我们在命令行上运行Python程序时会指定一个.py文件:

1$ python my_program.py

但是,我们还可以创建一个包含代码的目录或者压缩文件,并在其中包含一个__main__.py文件。然后,我们只需要在命令行上指定目录或者压缩文件的名称,main.py文件会自动执行:

1$ python my_program_dir
2$ python my_program.zip

你可以根据你的应用程序是否可以从这种运行方式中获益来决定是否使用__main__.py文件。


需要注意的是,main__模块通常不是来自__main.py文件。它可以是,但通常不是。当你运行像python my_program.py这样的脚本时,脚本会作为__main__模块而不是my_program模块运行。这也适用于作为python -m my_module运行的模块,或者其他几种方式。

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

参考答案:

main.py用于在zip文件中运行Python程序。当运行zip文件时,main.py文件会被执行。例如,如果zip文件的结构如下所示:

1test.zip
2    __main__.py

main.py文件的内容如下:

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

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

因此,当我们用python调用zip文件时,main.py文件会被执行。

参考答案:

如果你的脚本是一个目录或者ZIP文件,而不是一个单独的Python文件,在将"脚本"作为参数传递给Python解析器时,main.py文件将被执行。

参考答案:

你可以创建一个__main__.py文件,并且放在你的包(yourpackage)中,这样就可以通过以下方式执行它:

1$ python -m yourpackage

参考答案:

什么是__main__.py文件?

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

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

使用__main__.py文件可以实现相同的语义效果,下面是一个__main__.py文件的示例结构:

1.
2└── demo
3    ├── __init__.py
4    └── __main__.py

要查看示例,请将下面的代码粘贴到Python 3的shell中:

 1from pathlib import Path
 2
 3demo = Path.cwd() / 'demo'
 4demo.mkdir()
 5
 6(demo / '__init__.py').write_text("""
 7print('demo/__init__.py executed')
 8
 9def main():
10    print('main() executed')
11""")
12
13(demo / '__main__.py').write_text("""
14print('demo/__main__.py executed')
15
16from demo import main
17
18main()
19""")

我们可以将demo视为一个包,实际上可以导入它,这将执行__init__.py中的顶级代码(但不执行main函数):

1>>> import demo
2demo/__init__.py executed

当我们将该包用作程序的入口点时,我们会执行__main__.py中的代码,该代码首先引入__init__.py:

1$ python -m demo
2demo/__init__.py executed
3demo/__main__.py executed
4main() executed

你可以从官方文档中了解这一点。文档中说明如下:

 1__main__ — 顶级脚本环境
 2'__main__'是顶级代码执行的范围的名称。
 3当模块从标准输入、脚本或交互提示符读入时,该模块的__name__设置为'__main__'。
 4通过检查自己的__name__,模块可以发现它是否在主应用程序运行的范围中,
 5这允许一个常见的习语在模块作为脚本或通过“python -m”运行时条件地执行代码,
 6而当它被导入时则不运行:
 7if __name__ == '__main__':
 8    # 只有在作为脚本运行时才执行
 9    main()
10对于包,可以通过包含一个__main__.py模块来实现相同的效果,
11执行时-通过“-m”以及本身不会被添加到系统路径。

Zipped (zip文件)

你还可以将此目录压缩成一个单独的文件,并且在命令行上运行它,但请注意,压缩的包无法作为入口点来执行子包或子模块:

 1from pathlib import Path
 2
 3demo = Path.cwd() / 'demo2'
 4demo.mkdir()
 5
 6(demo / '__init__.py').write_text("""
 7print('demo2/__init__.py executed')
 8
 9def main():
10    print('main() executed')
11""")
12
13(demo / '__main__.py').write_text("""
14print('demo2/__main__.py executed')
15
16from __init__ import main
17
18main()
19""")

请注意,我们从__init__导入main,而不是从demo2导入-这个压缩的目录不是作为一个包来处理的,而是作为脚本的目录。因此,它必须在没有-m标志的情况下使用。

特别是与该问题相关的是,zipapp会导致压缩的目录默认执行__main__.py,而它会在__init__.py之前执行:

1$ python -m zipapp demo2 -o demo2zip
2$ python demo2zip
3demo2/__main__.py executed
4demo2/__init__.py executed
5main() executed

请注意,这个压缩的目录不是一个包-你不能导入它。

参考答案:

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

重要的区别是,如果不使用-m开关,会首先将“包”目录添加到路径中(即sys.path),然后以普通方式运行文件,而不带包语义。

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

这是一个非常重要的区别,无论相对导入是否有效,还有更重要的是,在不小心屏蔽系统模块时,它可以决定将导入的是什么。

示例:

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

1:~/PkgTest$ tree
2.
3├── pkgname
4│   ├── __main__.py
5│   ├── secondtest.py
6│   └── testmodule.py
7└── testmodule.py

其中__main__.py文件的内容如下:

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

(其他文件的定义方式类似,并有类似的打印输出)。

如果你在不使用-m开关的情况下运行它,你将得到以下结果。请注意,相对导入将失败,但更重要的是请注意已经选择了错误的testmodule(即相对于当前工作目录):

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

与使用-m开关运行时,你将得到你(希望)得到的结果:

1:~/PkgTest$ python3 -m pkgname
2Hello from pkgname.__main__.py. I am the file ~/PkgTest/pkgname/__main__.py
3I am being accessed from ~/PkgTest
4Hello from testmodule.py. I am the file ~/PkgTest/testmodule.py
5I am being accessed from ~/PkgTest
6Hello from secondtest.py. I am the file ~/PkgTest/pkgname/secondtest.py
7I am being accessed from ~/PkgTest

需要注意的是,没有-m开关应该避免使用。事实上,我会进一步说,我会以这种方式创建任何可执行的包,除非通过-m开关运行它们,否则它们将失败。

换句话说,我只会通过"相对导入"明确地导入"包内模块",假设所有其他导入代表的都是系统模块。如果有人在没有-m开关的情况下运行您的包,相对导入语句将抛出错误,而不是静默地运行错误的模块。


相关文章推荐