__main__ — 顶级代码环境 — Python 文档

来自菜鸟教程
Python/docs/3.10/library/ main
跳转至:导航、​搜索

__main__ — 顶层代码环境


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

  1. 程序顶层环境的名称,可以使用__name__ == '__main__'表达式查看; 和
  2. Python 包中的 __main__.py 文件。

这两种机制都与 Python 模块有关; 用户如何与他们互动以及他们如何彼此互动。 下面将详细解释它们。 如果您不熟悉 Python 模块,请参阅教程部分 Modules 中的介绍。

__name__ == '__main__'

导入 Python 模块或包时,__name__ 设置为模块的名称。 通常,这是没有 .py 扩展名的 Python 文件本身的名称:

>>> import configparser
>>> configparser.__name__
'configparser'

如果文件是包的一部分,__name__ 还将包含父包的路径:

>>> from concurrent.futures import process
>>> process.__name__
'concurrent.futures.process'

但是,如果模块在顶级代码环境中执行,则其 __name__ 设置为字符串 '__main__'

什么是“顶级代码环境”?

__main__ 是运行顶级代码的环境名称。 “顶级代码”是第一个开始运行的用户指定的 Python 模块。 它是“顶级”的,因为它导入了程序需要的所有其他模块。 有时,“顶级代码”被称为应用程序的 入口点

顶层代码环境可以是:

  • 交互式提示的范围:

    >>> __name__
    '__main__'
  • Python 模块作为文件参数传递给 Python 解释器:

    $ python3 helloworld.py
    Hello, world!
  • 使用 -m 参数传递给 Python 解释器的 Python 模块或包:

    $ python3 -m tarfile
    usage: tarfile.py [-h] [-v] (...)
  • Python 解释器从标准输入读取的 Python 代码:

    $ echo "import this" | python3
    The Zen of Python, by Tim Peters
    
    Beautiful is better than ugly.
    Explicit is better than implicit.
    ...
  • 使用 -c 参数传递给 Python 解释器的 Python 代码:

    $ python3 -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__,请参阅教程部分 Modules


惯用语

某些模块包含仅供脚本使用的代码,例如解析命令行参数或从标准输入获取数据。 如果像这样的模块是从不同的模块导入的,例如为了对其进行单元测试,脚本代码也会无意中执行。

这是使用 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 语句,则隐式返回)。

通过自己主动遵循这个约定,我们的模块在直接运行时将具有相同的行为(即 python3 echo.py),因为如果我们稍后将其打包为 pip 可安装包中的控制台脚本入口点,它将具有。

尤其要注意从 main 函数返回字符串。 sys.exit() 会将字符串参数解释为失败信息,所以你的程序会有一个退出代码1,表示失败,字符串将被写入 sys.stderr。 前面的 echo.py 示例说明了使用 sys.exit(main()) 约定。

也可以看看

Python 打包用户指南 包含有关如何使用现代工具分发和安装 Python 包的教程和参考集合。


Python 包中的 __main__.py

如果您不熟悉 Python 包,请参阅教程的 Packages 部分。 最常见的是,__main__.py 文件用于为包提供命令行界面。 考虑以下假设的包,“bandclass”:

bandclass
  ├── __init__.py
  ├── __main__.py
  └── student.py

__main__.py 将在使用 -m 标志直接从命令行调用包本身时执行。 例如:

$ python3 -m bandclass

此命令将导致 __main__.py 运行。 您如何利用此机制取决于您正在编写的包的性质,但在这种假设情况下,允许教师搜索学生可能是有意义的:

# bandclass/__main__.py

import sys
from .student import search_students

student_name = sys.argv[2] 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 文件。 因此,为了一致性,最小的 __main__.py 像上面提到的 venv 是首选。

也可以看看

有关标准库中具有最小 __main__.py 的包的示例,请参阅 venv。 它不包含 if __name__ == '__main__' 块。 您可以使用 python3 -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`!')

    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())

现在,如果我们启动我们的程序,结果将是这样的:

$ python3 start.py
Define the variable `my_name`!

程序的退出代码为 1,表示有错误。 用 my_name = "Dinsdale" 取消注释行修复程序,现在它以状态代码 0 退出,表示成功:

$ python3 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__ 作用域用于 pdbrlcompleter 的实现。