__main__
— 顶层代码环境¶
在 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__
来发现它是否在顶层环境中运行,这允许一种常见的惯用法,在模块不是从 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
时,将定义 echo
和 main
函数,但它们都不会被调用,因为 __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
。
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())
现在,如果我们启动程序,结果将如下所示
$ 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__
模块,并通过运行顶层代码来填充它。在我们的示例中,这是逐行运行并导入 namely
的 start
模块。反过来,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
的实现中。