__main__ — 顶层代码环境


在 Python 中,特殊名称 __main__ 用于两个重要的构造:

  1. 程序的顶层环境的名称,可以通过 __name__ == '__main__' 表达式来检查;以及

  2. 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__ 来判断自己是否在顶层环境中运行,这允许一种常见的惯用法,即在模块不是通过 import 语句初始化时有条件地执行代码:

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 被导入时,echomain 函数都会被定义,但它们都不会被调用,因为 __name__ != '__main__'

打包注意事项

main 函数通常用于创建命令行工具,通过将其指定为控制台脚本的入口点。当这样做时,pip 会将函数调用插入到一个模板脚本中,其中 main 的返回值会传递给 sys.exit()。例如:

sys.exit(main())

由于对 main 的调用被包装在 sys.exit() 中,预期是你的函数会返回一个可接受作为 sys.exit() 输入的值;通常是一个整数或 None(如果你的函数没有 return 语句,则隐式返回)。

通过主动遵循这个约定,我们的模块在直接运行时(即 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 文件不起作用。因此,为了一致性,推荐使用一个不带 __name__ 检查的最小化 __main__.py

参见

有关标准库中带有最小化 __main__.py 的包的示例,请参见 venv。它不包含 if __name__ == '__main__' 块。你可以使用 python -m venv [directory] 来调用它。

有关解释器可执行文件的 -m 标志的更多细节,请参见 runpy

有关如何运行打包为 .zip 文件的应用程序,请参见 zipapp。在这种情况下,Python 会在归档文件的根目录中查找 __main__.py 文件。

import __main__

无论一个 Python 程序是从哪个模块启动的,在同一个程序中运行的其他模块都可以通过导入 __main__ 模块来导入顶层环境的作用域(命名空间)。这并不会导入一个 __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`!')

    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

请注意,导入 __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__ 作用域在 pdbrlcompleter 的实现中使用。