​__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__ ,来发现它是否在顶层环境中运行。这是允许在模块没有从导入语句中初始化的情况下,有条件地执行代码的一个常见的语句:

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 的实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/253072.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(5)shell命令以及Linux的权限

写在前面 本章我们将重点讲解 Linux 权限,这是 Linux 基础部分中非常重要的一部分。内容比较干,我会稍稍正经些去讲解。话不多说,我们直接切入正题。 shell 命令及运行原理 严格意义上说的是一个操作系统,我们称之为 —— &…

MDK编译过程和文件类型

MDK是一款IDE软件,具有,编辑,编译,链接,下载,调试等等的功能。 1.编译器介绍: MDK可以编译C/C文件和汇编文件,MDK只是一款IDE软件,那他内部使用的是什么编译器呢&#x…

【已解决】Java zip解压时候 malformed input off : 4, length : 1

需求:通过页面上传ZIP文件后,对zip文件进行解压。 遇到的错误:在进行zip解压的时候错误如下: 先看报错前的: /*** 解压缩ZIP文件* param zipFile ZIP文件* param destDir 目标路径*/ public static void zipDecompre…

HIVE窗口函数

什么是窗口函数 hive中开窗函数通过over关键字声明;窗口函数,准确地说,函数在窗口中的应用;比如sum函数不仅可在group by后聚合,在可在窗口中应用; hive中groupby算子和开窗over,shuffle的逻辑…

时序数据库选型TimescaleDB

最近要做一个数字车间的物联网项目,数据存储成了首先要解决的问题,整个车间一共104台数控机床,1s钟采集1次数据,360024365*1043,279,744,000 ,一年要产生32亿条记录,这个数据量用常见的关系型数据库肯定是不…

phpMyAdmin的常见安装位置

nginx的日志显示有人一直在尝试访问phpMyAdmin的setup.php,用了各种位置。 其实我只有一个nginx,别的什么也没有。 47.99.136.156 - - [01:44:37 0800] "GET http://abc.com:80/phpMyAdmin/scripts/setup.php HTTP/1.0" 404 162 "-"…

新建vue3项目

三种方法 一. 第一种方式 1、操作步骤: 创建项目目录 vue create 项目名称选择配置方式 ? Please pick a preset: #选择一个配置 Default ([Vue 3] babel, eslint)Default ([Vue 2] babel, eslint)Manually select …

wordpress安装之正式开始安装wordpress

1、拉取wordpress镜像 docker pull wordpress 2、启动容器 启动容器,设置容器名为wordpress2并把80端口映射到宿主机的9988端口 docker run -it --name wordpress2 -p 9988:80 -d wordpress 3、查看容器状态 docker ps 4、安装wordpress博客程序 因为我们前面启…

「斗破年番」小医仙黑皇城遭调戏,五品丹换药材,获取菩提涎消息

Hello,小伙伴们,我是拾荒君。 《斗破苍穹年番》的第75集已经更新了,喜欢这部国漫的小伙伴应该都去观看了吧,拾荒君也是看了看这一集。在这一集中,萧炎成功地帮助吴昊等人摆脱了鹰爪老人的围困,然后便前往了黑皇城。 黑…

【JAVA-Day65】Java内部类深度解析

Java内部类深度解析 《Java内部类深度解析》摘要引言一、理解内部类1. 内部类的基本概念和语法1.1 什么是内部类?1.2 内部类的语法结构1.3 内部类的基本概念 2. 不同类型的内部类详解2.1 成员内部类2.2 静态内部类2.3 局部内部类2.4 匿名内部类 二、内部类与普通类的…

FL Studio2024破解版注册机及使用教程

FL Studio 2024破解版是一款非常实用的软件。该软件用于不同的目的,例如从音频中删除人声、管理音频以及更改不同的音频属性。此外,您还可以查看音频和歌曲的不同效果并管理其音量和大小。您还可以管理音乐和音频的自动化。您可以用它创作一首具有不同音…

深入理解——快速排序

目录 💡基本思想 💡基本框架 💡分割方法 ⭐Hoare版本 ⭐挖坑法 ⭐前后指针法 💡优化方法 ⭐三数取中法 ⭐小区间内使用插入排序 💡非递归实现快速排序 💡性能分析 💡基本思想 任取待排…

LeedCode刷题---滑动窗口问题(二)

顾得泉:个人主页 个人专栏:《Linux操作系统》 《C/C》 《LeedCode刷题》 键盘敲烂,年薪百万! 一、将X减到0的最小操作数 题目链接:将 x 减到 0 的最小操作数 题目描述 给你一个整数数组 nums 和一个整数 x 。每一…

函数和函数表达式

我们先来看三个式子 1️⃣ yf(x) 2️⃣ f(x)2x1 3️⃣ y2x1 先来明确一下概念,这三个式子的🟰两边总共有3种表达形式 y是函数最终输出的值f(x) 是整个函数运算过程2x1是具体的表达式 那么这三个式子分别是什么意思呢? yf(x)是对函数关系的…

一个简单的光线追踪渲染器

前言 本文参照自raytracing in one weekend教程,地址为:https://raytracing.github.io/books/RayTracingInOneWeekend.html 什么是光线追踪? 光线追踪模拟现实中的成像原理,通过模拟一条条直线在场景内反射折射,最终…

Cadence SPB17.4做Logo封装及添加中文丝印

Cadence SPB17.4 -Allegro - 做Logo封装及添加中文丝印 Chapter1 Cadence SPB17.4做Logo封装Chapter2 Allegro添加中文字体的简单有效方法Chapter3 Allegro添加Logo方法方法一方法二 Chapter4选择菜单栏Dimension——Create Detail命令对logo进行缩放设置,如下图所示…

【linux--进程通信之共享内存】

目录 一、共享内存的原理二、共享内存的数据结构三、共享内存使用的函数2.1ftok函数2.2shmget函数2.3shmctr函数2.4shmat函数2.5shmdt函数 四、实现进程通信 一、共享内存的原理 共享内存实际是操作系统在实际物理内存中开辟的一段内存。 共享内存实现进程间通信,是…

华为云创新动能涌现,浒墅关开启先进制造新纪元

编辑:阿冒 设计:沐由 穿境而过的京杭大运河,孕育了苏州浒墅关深厚的历史文化底蕴。千年延续不断的繁华,滋养了一代又一代奋进的浒墅关人。今天,一座国家级经开区挺立在这里,散发出创新创业的蓬勃活力。 苏州…

配置本地端口镜像示例

镜像概念 定义 镜像是指将指定源的报文复制一份到目的端口。指定源被称为镜像源,目的端口被称为观察端口,复制的报文被称为镜像报文。 镜像可以在不影响设备对原始报文正常处理的情况下,将其复制一份,并通过观察端口发送给监控…

dysmsapi

dysmsapi DY - SMS - API 短信服务接口 短信服务_SDK中心-阿里云OpenAPI开发者门户 <!-- 阿里dayu sms api短信群发接口 --><!-- https://mvnrepository.com/artifact/com.aliyun/dysmsapi20170525/2.0.24 --><dependency><groupId>com.aliyun&l…