在 Python 中,特殊名称 __main__
被用于两个重要的构造:
-
程序的最高层级环境的名称,可以使用
__name__ == '__main__'
表达式来检查它;以及 -
Python 包中的
__main__.py
文件。
这两种机制都有 Python 模块有关;用户如何与它们交互以及它们之间如何交互。 下文将进行详细说明。 如果你还不了解 Python 模块,请查看教程 模块 一节的介绍。
__name__ == '__main__'
当一个 Python 模块或包被导入时,__name__
会被设为模块的名称。 通常,这将是 Python 文件本身的名称去掉 .py
后缀:
>>>
>>> import configparser >>> configparser.__name__ 'configparser'
如果文件是包的组成部分,则 __name__
还将包括父包的路径:
>>>
>>> from concurrent.futures import process >>> process.__name__ 'concurrent.futures.process'
不过,如果模块是在最高层级代码环境中执行的,则它的 __name__
会被设为字符串 '__main__'
。
什么是“最高层级代码环境”?
__main__
是最高层级代码运行所在环境的名称。 “最高层级代码”即用户指定最先启动运行的 Python 模块。 它被称为“最高层级”是因为它将导入程序所需的所有其他模块。 有时“最高层级代码”也被称为应用的 入口点。
最高层级代码环境可以是:
-
一个交互提示符的作用域:
>>>>>> __name__ '__main__'
-
作为文件参数传给 Python 解释器的 Python 模块:
$ python helloworld.py Hello, world!
-
作为 -m 参数传给 Python 解释器的 Python 模块或包:
$ python -m tarfile usage: tarfile.py [-h] [-v] (...)
-
Python 解释器从标准输入中读取的 Python 代码:
$ echo "import this" | python The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. ...
-
作为 -c 参数传递给 Python 解释器的 Python 代码:
$ python -c "import this" The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. ...
在以上每个情形中,顶级模块的 __name__
被设置为 '__main__'
。
因此,一个模块可以通过检查自己的 __name__
,来发现它是否在顶层环境中运行。这是允许在模块没有从导入语句中初始化的情况下,有条件地执行代码的一个常见的语句:
if __name__ == '__main__': # Execute when the module is not initialized from an import statement. ...
参见
关于在所有情况下 __name__
是如何设置的细节,请看教程部分 模块 。
常见用法
有些模块包含了仅供脚本使用的代码,比如解析命令行参数或从标准输入获取数据。 如果这样的模块被从不同的模块中导入,例如为了单元测试,脚本代码也会无意中执行。
这就是 if __name__ == '__main__'
代码块的用武之地。除非模块在顶层环境中被执行,否则该块内的代码不会运行。
将尽可能少的语句放在位于 if __name__ == '__main__'
之下的代码块中可以提高代码的清晰度和准确度。 通常,将由一个名为 main
的函数来封装程序的主要行为:
# echo.py import shlex import sys def echo(phrase: str) -> None: """A dummy wrapper around print.""" # for demonstration purposes, you can imagine that there is some # valuable and reusable logic inside this function print(phrase) def main() -> int: """Echo the input arguments to standard output""" phrase = shlex.join(sys.argv) echo(phrase) return 0 if __name__ == '__main__': sys.exit(main()) # next section explains the use of sys.exit
请注意,如果模块没有将代码封装在 main
函数内,而是直接放在 if __name__ == '__main__'
块内,那么这个 phrase
变量对整个模块来说就是全局变量。 这很容易出错,因为模块内的其他函数可能会无意中使用全局变量而不是局部名称。 一个 main
函数解决了这个问题。
使用 main
函数有一个额外的好处,就是 echo
函数本身是孤立的,可以在其他地方导入。当 echo.py
被导入时,echo
和 main
函数将被定义,但它们都不会被调用,因为 __name__ != '__main__'
。
打包考量
main
函数经常被用来创建命令行工具,把它们指定为控制台脚本的入口点。 当这样做时,pip 将函数调用插入到模板脚本中,其中 main
的返回值被传递到 sys.exit() 。例如:
sys.exit(main())
由于 main
调用被包裹在 sys.exit() 中,期望你的函数将返回一些可被 sys.exit() 作为输入而接受的值;通常为一个整数或 None
(如果你的函数没有返回语句,则隐含返回)。
通过主动遵循这一惯例,我们的模块在直接运行时 (即 python echo.py
) 会有相同的行为,当我们以后把它打包成可用 pip 安装的软件包的控制台脚本入口时也会如此。
特别的是,要小心从你的 main
函数中返回字符串。 sys.exit() 将把一个字符串参数解释为失败信息,所以你的程序将有一个 1
的退出代码,表示失败。并且这个字符串将被写入 sys.stderr 。 前面的 echo.py
例子举例说明了使用 sys.exit(main())
的约定。
参见
Python 打包用户指南 包含了一系列关于如何用现代工具分发和安装 Python 包的教程和参考资料。
Python 包中的 __main__.py
如果你不熟悉Python包,请参阅本教程的 包 一节。最常见的是, __main__.py
文件被用来为一个包提供命令行接口。假设有下面这个虚构的包,"bandclass":
bandclass ├── __init__.py ├── __main__.py └── student.py
当使用 -m 标志从命令行直接调用软件包本身时,将执行 __main__.py
。比如说。
$ python -m bandclass
这个命令将导致 __main__.py
的运行。你如何利用这一机制将取决于你所编写的软件包的性质,但在这个假设的案例中,允许教师搜索学生可能是有意义的:
# bandclass/__main__.py import sys from .student import search_students student_name = sys.argv[1] if len(sys.argv) >= 2 else '' print(f'Found student: {search_students(student_name)}')
注意, from .student import search_students
是一个相对导入的例子。 这种导入方式可以在引用一个包内的模块时使用。 更多细节,请参见教程 模块 中的 相对导入 一节。
常见用法
__main__.py
的内容通常不会用 if __name__ == '__main__'
块围起来。 相反,这些文件会保持简短并从其他模块导入函数来执行。 这样其他模块就可以很容易地进行单元测试并可以适当地重用。
如果使用,一个 if __name__ == '__main__'
区块仍然会像预期的那样对包内的 __main__.py
文件起作用,因为如果导入,它的 __name__
属性将包括包的路径:
>>>
>>> import asyncio.__main__ >>> asyncio.__main__.__name__ 'asyncio.__main__'
但这对 .zip 文件的根目录中的 __main__.py
文件不起作用。 因此,为了保持一致性,像下面提到的 venv 这样的最小 __main__.py
是首选。
参见
请参阅 venv 以获取标准库中具有最小化 __main__.py
的软件包示例。 它不包含 if __name__ == '__main__'
代码块。 你可以用 python -m venv [directory]
来发起调用。
参见 runpy 以了解更多关于 -m 标志对解释器可执行包的细节。
参见 zipapp 了解如何运行打包成 .zip 文件的应用程序。在这种情况下,Python 会在归档文件的根目录下寻找一个 __main__.py
文件。
import __main__
不管 Python 程序是用哪个模块启动的,在同一程序中运行的其他模块可以通过导入 __main__
模块来导入顶级环境的范围 ( namespace )。这并不是导入一个 __main__.py
文件,而是导入使用特殊名称 '__main__'
的哪个模块。
下面是一个使用 __main__
命名空间的模块的例子:
# namely.py import __main__ def did_user_define_their_name(): return 'my_name' in dir(__main__) def print_user_name(): if not did_user_define_their_name(): raise ValueError('Define the variable `my_name`!') if '__file__' in dir(__main__): print(__main__.my_name, "found in file", __main__.__file__) else: print(__main__.my_name)
该模块的用法示例如下:
# start.py import sys from namely import print_user_name # my_name = "Dinsdale" def main(): try: print_user_name() except ValueError as ve: return str(ve) if __name__ == "__main__": sys.exit(main())
现在,如果我们启动我们的程序,结果会是这样的:
$ python start.py Define the variable `my_name`!
该程序的退出代码为 1 ,表明有错误。取消对 my_name = "Dinsdale"
这一行的注释,就可以修复程序,现在它的退出状态代码为 0 ,表示成功。
$ python start.py Dinsdale found in file /path/to/start.py
请注意,导入 __main__
不会导致无意中运行旨在用于脚本的顶层代码的问题,这些代码被放在模块 start
的 if __name__ == "__main__"
块中。为什么这样做?
Python 解释器启动时会在 sys.modules 中插入一个空的 __main__
模块,并通过运行最高层级代码来填充它。 在我们的例子中这就是 start
模块,它逐行运行并导入 namely
。 相应地,namely
会导入 __main__
(它实际上就是 start
)。 这就是一个导入循环! 幸运的是,由于部分填充的 __main__
模块存在于 sys.modules 中,Python 会将其传递给 namely
。 请参阅导入系统的参考文档中 有关 __main__ 的特别考量 来了解其中的详情。
Python REPL 是另一个 "顶层环境 "的例子,所以在 REPL 中定义的任何东西都成为 __main__
范围的一部分:
>>>
>>> import namely >>> namely.did_user_define_their_name() False >>> namely.print_user_name() Traceback (most recent call last): ... ValueError: Define the variable `my_name`! >>> my_name = 'Jabberwocky' >>> namely.did_user_define_their_name() True >>> namely.print_user_name() Jabberwocky
注意,在这种情况下, __main__
范围不包含 __file__
属性,因为它是交互式的。
__main__
范围用于 pdb 和 rlcompleter 的实现。