Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 397 – Windows Python 启动器

作者:
Mark Hammond <mhammond at skippinet.com.au>, Martin von Löwis <martin at v.loewis.de>
状态:
最终
类型:
标准跟踪
创建:
2011年3月15日
Python 版本:
3.3
历史记录:
2011年7月21日,2011年5月17日,2011年3月15日
决议:
Python-Dev 消息

目录

摘要

本 PEP 描述了 Windows 平台的 Python 启动器。Python 启动器是一个单一的可执行文件,它使用多种启发式方法来定位 Python 可执行文件并使用指定的命令行启动它。

基本原理

Windows 提供“文件关联”,以便将可执行文件与扩展名关联起来,允许在某些上下文中直接执行脚本(例如,在 Windows 资源管理器中双击文件)。到目前为止,一直使用“最后安装的 Python 优先”的策略,虽然不理想,但由于 Python 2.x 版本中保守的更改,通常是可行的。由于 Python 3.x 脚本通常在语法上与 Python 2.x 脚本不兼容,因此必须使用不同的策略来允许具有“.py”扩展名的文件根据脚本目标的 Python 版本使用不同的可执行文件。这将通过借用另一个操作系统的现有实践来完成 - 脚本将能够通过“shebang”行指定它们所需的 Python 版本,如下所述。

类 Unix 操作系统(在本 PEP 中简称为“Unix”)允许将脚本作为可执行映像执行,方法是检查脚本是否存在指定用于运行脚本的实际可执行文件的“shebang”行。这在evecve(2) 手册页[1]中进行了详细描述,虽然将为此功能创建用户文档,但出于本 PEP 的目的,该手册页描述了一个有效的 shebang 行。

此外,这些操作系统在众所周知的目录中提供了指向 Python 可执行文件的符号链接。例如,许多系统将有一个链接 /usr/bin/python,它引用安装在操作系统下的特定版本的 Python。这些符号链接允许执行 Python 而无需考虑 Python 实际安装在机器上的位置(例如,无需在 shebang 行或PATH中引用 Python 实际安装的路径)。PEP 394“类 Unix 系统上的“python”命令”描述了用于更细粒度地指定特定 Python 版本的其他约定。

这两种功能相结合,提供了一种可移植且某种程度上可预测的方式来交互式启动 Python 以及允许 Python 脚本执行。本 PEP 描述了启动器的实现,该启动器可以为 Windows 平台上的 Python 提供相同的好处,因此允许启动器成为与“.py”文件关联的可执行文件以同时支持多个 Python 版本。

虽然本 PEP 提供了使用 shebang 行的功能,该功能应该在 Windows 和 Unix 上都能工作,但这并不是本 PEP 的主要动机 - 主要动机是允许指定特定版本,而无需发明新的语法或约定来描述它。

规范

本 PEP 指定了启动器的功能;原型实现在[3]中提供,它将与 Python 的 Windows 安装程序一起分发,但也将单独提供(但与 Python 安装程序一起发布)。只要此处规定的功能继续有效,就可以向启动器添加新功能。

安装

启动器有两种版本 - 一种是控制台程序,另一种是“windows”(即 GUI)程序。这两个启动器分别对应于当前与 Python 一起提供的‘python.exe’和‘pythonw.exe’可执行文件。控制台启动器将命名为‘py.exe’,Windows 启动器命名为‘pyw.exe’。“windows”(即 GUI)版本的启动器将尝试定位并启动 pythonw.exe,即使虚拟 shebang 行仅指定“python” - 实际上,尾随的‘w’表示法根本不受虚拟 shebang 行支持。

如果由特权用户安装,则启动器将安装到 Windows 目录(请参阅下面的讨论)。独立安装程序会询问安装程序的替代位置,并将该位置添加到用户的PATH中。

Windows 目录中的安装是 32 位可执行文件(请参阅讨论);独立安装程序还可以选择安装 64 位版本的启动器。

启动器安装在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CurrentVersion\SharedDLLs中,并带有一个引用计数。它包含一个与它一起分发的 pythonXY.dll 的版本号匹配的版本资源。独立安装将用较新版本的启动器覆盖旧版本。独立版本在基于它们的 CPython 版本的FIELD3中使用0x10的发布级别。

安装后,“控制台”版本的启动器与 .py 文件关联,“windows”版本与 .pyw 文件关联。

启动器不与特定版本的 Python 绑定 - 例如,与 Python 3.3 一起分发的启动器应该能够定位和执行任何 Python 2.x 和 Python 3.x 版本。但是,启动器二进制文件具有与它们随其发布的 Python 二进制文件中的版本资源相同的版本资源。

Python 脚本启动

启动器仅限于启动 Python 脚本。它并非旨在用作通用脚本启动器或 shebang 处理器。

启动器支持[1]中描述的 shebang 行语法,包括列出的所有限制。

启动器支持引用具有任何(正则表达式)前缀“/usr/bin/”、“/usr/local/bin”和“/usr/bin/env *”的 Python 可执行文件的 shebang 行,以及未指定

例如,即使在相对 Windows 目录“\usr\bin”中不太可能存在可执行文件,shebang 行‘#! /usr/bin/python’也应该可以工作。这意味着许多脚本可以使用单个 shebang 行,并且很可能在 Unix 和 Windows 上无需修改即可工作。

启动器将支持可执行文件的完全限定路径。虽然这会使脚本本身不可移植,但这确实是 Unix 提供的功能,在某些情况下对 Windows 用户很有用。

启动器将能够支持除 CPython 之外的其他实现,例如 jython 和 IronPython,但鉴于 Unix 上缺少通用链接(例如“/usr/bin/jython”)以及启动器无法自动定位这些实现的安装位置在 Windows 上,启动器将通过自定义选项支持此功能。利用此功能的脚本将不可移植(因为必须设置这些自定义选项以反映启动器运行的机器的配置),但此功能仍然被认为是有价值的。

在 Unix 上,用户可以通过调整 /usr/bin 中的链接以指向所需的版本来控制使用哪个特定版本的 Python。由于 Windows 上的启动器将不使用 Windows 链接,因此将使用自定义选项(通过环境变量和 INI 文件公开)来覆盖确定将使用哪个版本的 Python 的语义。例如,虽然“/usr/bin/python2”的 shebang 行将自动定位 Python 2.x 实现,但环境变量可以覆盖将选择哪个 Python 2.x 实现。类似地,对于“/usr/bin/python”和“/usr/bin/python3”。这将在本 PEP 的后面详细说明。

Shebang 行解析

如果第一个命令行参数不以破折号(‘-‘)字符开头,则将尝试将其作为文件打开并根据[1]中的规则解析 shebang 行。

#! interpreter [optional-arg]

解析后,将根据以下规则对命令进行分类

  • 如果命令以自定义命令的定义开头,后跟空格字符(包括换行符),则将使用自定义命令。有关自定义命令的描述,请参见下文。
  • 启动器将定义一组被认为是与 Unix 兼容的用于启动 Python 的命令前缀,即“/usr/bin/python”、“/usr/local/bin/python”、“/usr/bin/env python”和“python”。如果命令以这些字符串之一开头,则将被视为“虚拟命令”,并将使用 Python 版本限定符(如下所述)中描述的规则来查找要使用的可执行文件。
  • 否则,假定命令可以直接执行 - 即完全限定路径(或对PATH上可执行文件的引用),后跟可选的参数。字符串的内容不会被解析 - 它将在附加脚本名称和启动器命令行参数后直接传递给 Windows CreateProcess 函数。这意味着将使用 CreateProcess 使用的规则,包括如何处理相对路径名和没有扩展名的可执行文件引用。值得注意的是,不会使用 Windows 命令处理器,因此不会使用命令处理器使用的特殊规则(例如,自动附加除“.exe”之外的其他扩展名,支持批处理文件等)。

鼓励使用“虚拟”shebang 行,因为这应该允许指定可在多个操作系统和同一操作系统的不同安装上工作的可移植 shebang 行。

如果第一个参数无法作为文件打开,或者找不到有效的 shebang 行,则启动器将执行类似于找到‘#!python’ shebang 行的操作 - 即,将定位默认的 Python 解释器并将参数传递给它。但是,如果找到有效的 shebang 行,但该行指定的进程无法启动,则不会启动默认解释器 - 创建指定子进程的错误将导致启动器显示适当的消息并以特定退出代码终止。

配置文件

启动器将搜索两个 .ini 文件 - 当前用户“应用程序数据”目录中的 py.ini(即,通过使用 CSIDL_LOCAL_APPDATA 调用 Windows 函数 SHGetFolderPath 返回的目录,在 Vista+ 上为 %USERPROFILE%\AppData\Local,在 XP 上为 %USERPROFILE%\Local Settings\Application Data)以及启动器所在目录中的 py.ini。 这些 .ini 文件同时用于启动器的“控制台”版本(即 py.exe)和“Windows”版本(即 pyw.exe)。

“应用程序目录”中指定的自定义设置将优先于可执行文件旁边的设置,因此用户(可能没有启动器旁边 .ini 文件的写访问权限)可以覆盖该全局 .ini 文件中的命令。

Shebang 行中的虚拟命令

虚拟命令是以预计在 Unix 平台上可行的字符串开头的 Shebang 行 - 例如“/usr/bin/python”、“/usr/bin/env python”和“python”。 可选地,虚拟命令后面可以附加版本限定符(见下文),例如“/usr/bin/python2”或“/usr/bin/python3.2”。 执行的命令基于下面“Python 版本限定符”中描述的规则。

自定义命令

启动器将支持在 Windows .ini 文件(即,Windows 函数 GetPrivateProfileString 可以解析的文件)中定义“自定义命令”的功能。 可以创建一个名为“[commands]”的节,其中键名定义虚拟命令,而值指定要用于此虚拟命令的实际命令行。

例如,如果 INI 文件的内容如下:

[commands]
vpython=c:\bin\vpython.exe -foo

那么脚本“doit.py”中的 Shebang 行“#! vpython”将导致启动器使用命令行 c:\bin\vpython.exe -foo doit.py

.ini 文件的名称、位置和搜索顺序的详细信息在启动器文档 [4]中。

Python 版本限定符

一些描述的功能允许使用可选的 Python 版本限定符。

版本限定符以主版本号开头,后面可以可选地跟随一个句点(“.”)和一个次版本规范。 如果指定了次版本规范,则后面可以可选地跟随“ -32”以指示使用该版本的 32 位实现。 请注意,不需要“ -64”限定符,因为这是默认实现(见下文)。

在安装了相同(主版本.次版本)Python 版本的 32 位和 64 位实现的 64 位 Windows 上,始终优先使用 64 位版本。 这对于启动器的 32 位和 64 位实现都适用 - 如果可用,32 位启动器将优先执行指定版本的 64 位 Python 安装。 这样,只需知道 PC 上安装了哪些版本,而无需考虑安装的顺序(即,无需知道最后安装的是 32 位还是 64 位版本的 Python 和相应的启动器),就可以预测启动器的行为。 如上所述,可以在版本规范上使用可选的“ -32”后缀来更改此行为。

如果在命令中找不到版本限定符,则可以设置环境变量 PY_PYTHON 以指定默认版本限定符 - 默认值为“2”。 请注意,此值可以仅指定主版本(例如“2”)或主版本.次版本限定符(例如“2.6”,甚至主版本.次版本-32)。

如果找不到次版本限定符,则可以设置环境变量 PY_PYTHON{major}(其中 {major} 是如上所述确定的当前主版本限定符)以指定完整版本。 如果找不到此类选项,启动器将枚举已安装的 Python 版本,并使用为该主版本找到的最新次版本发布,这很可能是(但不能保证)该系列中最近安装的版本。

除了环境变量之外,还可以在启动器使用的 .INI 文件中配置相同的设置。 INI 文件中的节称为 [defaults],键名将与环境变量相同,但没有前导 PY_ 前缀(并注意 INI 文件中的键名不区分大小写)。 环境变量的内容将覆盖 INI 文件中指定的内容。

命令行处理

仅检查第一个命令行参数是否存在 Shebang 行,并且仅当该参数不以“ -”开头时才检查。

如果唯一的命令行参数是“ -h”或“ --help”,启动器将打印一个小横幅和命令行用法,然后将参数传递给默认的 Python。 这将导致打印启动器的帮助信息,然后打印 Python 本身的帮助信息。 启动器的输出将清楚地表明扩展帮助信息来自启动器而不是 Python。

作为交互式启动 Python 的让步,启动器将支持第一个命令行参数可选地为连字符(“ -”)后跟如上所述的版本限定符,以指定使用特定的版本。 例如,虽然“py.exe”可能会找到并启动安装的最新 Python 2.x 实现,但诸如“py.exe -3”之类的命令行可以指定启动最新的 Python 3.x 实现,而“py.exe -2.6-32”可以指定找到并启动 32 位实现的 Python 2.6。 如果希望使用 -3 标志启动 Python 2.x 实现,则命令行需要类似于“py.exe -2 -3”(或者当然也可以手动启动 Python 的特定版本,而无需使用此启动器)。 请注意,此功能不能与 Shebang 处理一起使用,因为扫描 Shebang 行的文件和此参数都必须是第一个参数,因此是互斥的。

所有其他参数将原封不动地传递给子 Python 进程。

进程启动

启动器为交互式工作的 Python 开发人员提供了一些便利 - 例如,在没有命令行参数的情况下启动启动器将启动没有命令行参数的默认 Python。 此外,将支持命令行参数以允许交互式启动特定的 Python 版本 - 但是,这些便利性不得损害启动脚本的主要目的,并且如果需要,必须易于避免。

启动器创建一个子进程来启动实际的解释器。 请参见下面的“讨论”以了解其原理。

讨论

启动器安装到 Windows 目录中而不是 System32 目录中可能会令人惊讶。 原因是 System32 目录不在 64 位系统上运行的 32 位进程的 Path 中。 但是,Windows 目录始终位于路径中。

安装到 Windows 目录中的启动器是 32 位可执行文件,以便 32 位 CPython 安装程序可以为 32 位和 64 位 Windows 安装提供相同的二进制文件。

理想情况下,启动器进程将在同一进程中直接执行 Python,主要是因为启动器进程的父进程可以终止启动器并使 Python 解释器终止。 如果启动器将 Python 作为子进程执行,并且启动器的父进程终止了启动器,则 Python 进程将不受影响。

但是,这种方法存在许多实际问题。 Windows 不支持 Unix 函数的 execv* 系列,因此这只能通过启动器动态加载 Python DLL 来完成,但这将产生许多副作用。 最严重的副作用是 sys.executable 的值将引用启动器而不是 Python 实现。 许多 Python 脚本使用 sys.executable 的值来启动子进程,如果使用启动器,这些脚本可能会无法按预期工作。 考虑一个 Shebang 行为“#! /usr/bin/python3”的“父”脚本,该脚本尝试通过 sys.executable 启动子脚本(没有 Shebang) - 目前,子脚本使用与运行父脚本的完全相同的版本启动。 如果 sys.executable 指向启动器,则子脚本可能会使用 Python 2.x 版本执行,并且可能会因 SyntaxError 而失败。

另一个障碍是支持使用上面描述的“自定义命令”功能的替代 Python 实现,其中无法将命令动态加载到正在运行的可执行文件中。

最后一个障碍是上面关于 64 位和 32 位程序的规则 - 32 位启动器将无法加载 64 位版本的 Python,反之亦然。

考虑到这些因素,启动器将在子进程中执行其命令,在子进程执行期间保持活动状态,然后以子进程返回的相同退出代码终止。 为了解决启动器的终止不会杀死子进程的问题,将使用 Win32 作业 API 来安排在父进程终止时自动杀死子进程(尽管子进程的子进程将继续,就像现在一样)。 由于此 Windows API 在 Windows XP 及更高版本中可用,因此此启动器在 Windows 2000 或更早版本上将无法工作。

参考文献


来源:https://github.com/python/peps/blob/main/peps/pep-0397.rst

上次修改时间:2023-09-09 17:39:29 GMT