"__main__" --- 最高层级代码环境
*******************************

======================================================================

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

1. 程序的最高层级环境的名称，可以使用 "__name__ == '__main__'" 表达式
   来检查它；以及

2. Python 包中的 "__main__.py" 文件。

这两种机制都有 Python 模块有关；用户如何与它们交互以及它们之间如何交互
。 下文将进行详细说明。 如果你还不了解 Python 模块，请查看教程 模組
(Module) 一节的介绍。


"__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 模块:

        $ 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__" 是如何设置的细节，请看教程部分 模組
     (Module) 。


常见用法
--------

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

这就是 "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" （如果你的函
数没有返回语句，则隐含返回）。

通过主动遵循这一惯例，我们的模块在直接运行时（即 "python3 echo.py" ）
会有相同的行为，如果我们以后把它打包成可用 pip 安装的包中的控制台脚本
入口，它也会有相同的行为。

特别的是，要小心从你的 "main" 函数中返回字符串。 "sys.exit()" 将把一个
字符串参数解释为失败信息，所以你的程序将有一个 "1" 的退出代码，表示失
败。并且这个字符串将被写入 "sys.stderr" 。 前面的 "echo.py" 例子举例说
明了使用 "sys.exit(main())" 的约定。

也參考: Python 打包用户指南 包含了一系列关于如何用现代工具分发和安装 Python
     包的教程和参考资料。


Python 包中的 "__main__.py"
===========================

如果你不熟悉Python包，请参阅本教程的 套件 (Package) 一节。最常见的是，
"__main__.py" 文件被用来为一个包提供命令行接口。假设有下面这个虚构的包
，"bandclass":

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

当使用 "-m" 标志从命令行直接调用软件包本身时，将执行 "__main__.py" 。
比如说。

   $ 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" 是一个相对导入的例子。
这种导入方式可以在引用一个包内的模块时使用。 更多细节，请参见教程 模組
(Module) 中的 套件內引用 一节。


常见用法
--------

"__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" 是首选。

也參考:

  See "venv" for an example of a package with a minimal "__main__.py"
  in the standard library. It doesn't contain a "if __name__ ==
  '__main__'" block. You can invoke it with "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())

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

   $ 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__" 范围用于 "pdb" 和 "rlcompleter" 的实现。
