外部管理的环境#

虽然一些 Python 安装完全由安装 Python 的用户管理,但其他安装可能由其他方式(例如 Linux 发行版中的操作系统包管理器,或作为应用程序中捆绑的 Python 环境,该应用程序带有专门的安装程序)提供和管理。

尝试使用传统的 Python 打包工具来操作此类环境,最好的情况是令人困惑,最坏的情况是彻底破坏整个底层操作系统。文档和互操作性指南只能在一定程度上解决此类问题。

此规范定义了一个 EXTERNALLY-MANAGED 标记文件,允许 Python 安装向 Python 特定的工具(例如 pip)指示它们既不将包安装到解释器的默认安装环境中,也不将其从中删除,而应引导最终用户使用 Python 虚拟环境

它还标准化了 sysconfig 方案的解释,以便如果 Python 特定的包管理器即将在解释器范围的上下文中安装包,它可以以一种避免与外部包管理器冲突并降低风险的方式进行安装破坏外部包管理器提供的软件。

术语#

本规范中使用的几个术语在其所跨越的上下文中具有多个含义。为了清楚起见,本规范以特定方式使用以下术语

发行版

“发行版”的简称,各种软件的集合,理想情况下设计为可以正常协同工作,包括(在本文件中相关的上下文中)Python 解释器本身、用 Python 编写的软件以及用其他语言编写的软件。也就是说,这是在“Linux 发行版”或“伯克利软件发行版”等短语中使用的含义。

发行版可以是其自己的操作系统 (OS),例如 Debian、Fedora 或 FreeBSD。它也可以是安装在现有操作系统之上的覆盖发行版,例如 Homebrew 或 MacPorts。

本文档使用简称“发行版”,因为术语“发行版”在 Python 打包上下文中具有另一个含义:单个 Python 语言软件的源或二进制发行包,即 setuptools.dist.Distribution 或“sdist”的含义。为了避免混淆,本文档根本不使用“发行版”这个普通术语。在 Python 打包意义上,它使用完整的短语“发行包”或只是“包”(见下文)。

发行版的提供者——收集和发布软件并进行任何必要修改的团队或公司——是其发行商

可以在 Python 中安装和使用的软件单元。也就是说,这指的是 Python 特定的打包工具倾向于称为 发行包 或简单地称为“发行版”的内容;口语缩写“包”用于 Python 包索引的意义。

本文档不使用“包”来指包含 Python 模块的可导入名称,尽管在许多情况下,发行包由具有相同名称的单个可导入包组成。

本文档通常不使用术语“包”来指发行版的包管理器(例如 .deb.rpm 文件)的安装单元。在需要时,它使用诸如“发行版的包”之类的措辞。(同样,在许多情况下,Python 包都包含在发行版的包中,该包的名称类似于 python- 加上 Python 包名称。)

Python 特定的包管理器

一种工具,用于以符合 Python 打包标准的方式安装、升级和/或移除 Python 包。最流行的 Python 特定包管理器是 pip;其他示例包括旧的 Easy Install 命令 以及直接使用 setup.py 命令。

(请注意,easy_install 命令已在 2021 年 1 月 23 日发布的 setuptools 版本 52 中移除。)

Conda 有点特殊,因为 conda 命令可以安装的不仅仅是 Python 包,在某些方面更像是一个发行版包管理器。由于 conda 命令通常只在 Conda 创建的环境中运行,因此本文档中的大多数问题不适用于作为 Python 特定包管理器运行的 conda。)

发行版包管理器

一种工具,用于在已安装的发行版实例中安装、升级和/或移除发行版的包,该工具既可以安装 Python 包,也可以安装非 Python 包,因此通常有自己的已安装软件数据库,与 已安装发行版数据库 无关。示例包括 aptdpkgdnfrpmpacmanbrew。显着特征是,如果一个包是由发行版包管理器安装的,那么以满足 Python 特定包管理器的方式移除或升级它通常会使发行版包管理器处于不一致状态。

本文档还在某些上下文中使用“外部包管理器”或“系统的包管理器”等短语来指代发行版包管理器。

影子

对已安装的 Python 包进行影子操作是指让其他一些包在导入时优先,而无需从影子包中移除任何文件。这需要在 sys.path 上进行多个条目:如果包 A 2.0 在一个 sys.path 条目中安装模块 a.py,而包 A 1.0 在一个较后的 sys.path 条目中安装模块 a.py,那么 import a 返回来自前者的模块,我们称 A 2.0 对 A 1.0 进行影子操作。

概述#

本规范有两方面。

首先,它描述了 Python 解释器发行商将解释器标记为其包由 Python 外部手段管理的一种方式,以便 pip 等 Python 特定工具不应以任何方式(添加、升级/降级或移除)更改解释器全局 sys.path 中的已安装包,除非明确覆盖。它还为发行商提供了一种指示如何使用虚拟环境作为替代方法的方法。

这是一种选择加入机制:默认情况下,从上游源代码编译的 Python 解释器不会被如此标记,因此使用自编译解释器或未明确标记其解释器的发行版运行 pip install 将按其一直以来的方式工作。

其次,它设定了一个规则,即在将包安装到解释器的全局上下文中(无论是到未标记的解释器,还是覆盖标记)时,Python 特定包管理器应仅修改或删除 sysconfig 方案目录中的文件,它们将在其中创建文件。这允许 Python 解释器发行商设置两个目录,一个用于其自己管理的包,另一个用于最终用户安装的非托管包,并确保安装非托管包不会删除(或覆盖)外部包管理器拥有的文件。

将解释器标记为使用外部包管理器#

在 Python 特定包安装程序(即 pip 等工具,而不是 apt 等外部工具)将包安装到某个 Python 上下文之前,它应该默认进行以下检查

  1. 是否在虚拟环境外运行?它可以通过以下方式确定 sys.prefix == sys.base_prefix

  2. sysconfig.get_path("stdlib", sysconfig.get_default_scheme()) 标识的目录中是否存在 EXTERNALLY-MANAGED 文件?

如果这两个条件都为真,则安装程序应退出并显示一条错误消息,指出在此 Python 解释器的目录中安装包在虚拟环境外被禁用。

安装程序应允许用户覆盖这些规则,例如命令行标志 --break-system-packages。此选项不应默认启用,并且应带有其使用有风险的含义。

EXTERNALLY-MANAGED 文件是一个 INI 样式的元数据文件,旨在由标准库 configparser 模块解析。如果文件可以使用 UTF-8 编码通过 configparser.ConfigParser(interpolation=None) 解析,并且它包含一个节 [externally-managed],则安装程序应查找文件中指定的错误消息并将其作为其错误的一部分输出。如果 locale.getlocale(locale.LC_MESSAGES) 返回的元组的第一个元素,即语言代码,不是 None,则它应查找错误消息作为名为 Error- 后跟语言代码的键的值。如果该键不存在,并且语言代码包含下划线或连字符,则它应查找名为 Error- 后跟下划线或连字符之前语言代码部分的键。如果它找不到这两个键中的任何一个,或者语言代码是 None,则它应查找一个简单命名的键 Error

如果安装程序无法在文件中找到错误消息(可能是因为无法解析文件或因为没有合适的错误键),则安装程序应仅使用其自己的预定义错误消息,该消息应建议用户创建虚拟环境来安装包。

sys.path 中管理库的非 Python 特定包管理器的软件分发商通常应在其标准库目录中提供一个 EXTERNALLY-MANAGED 文件。例如,Debian 可能会在 /usr/lib/python3.9/EXTERNALLY-MANAGED 中提供一个文件,其中包含类似以下内容

[externally-managed]
Error=To install Python packages system-wide, try apt install
 python3-xyz, where xyz is the package you are trying to
 install.

 If you wish to install a non-Debian-packaged Python package,
 create a virtual environment using python3 -m venv path/to/venv.
 Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
 sure you have python3-full installed.

 If you wish to install a non-Debian packaged Python application,
 it may be easiest to use pipx install xyz, which will manage a
 virtual environment for you. Make sure you have pipx installed.

 See /usr/share/doc/python3.9/README.venv for more information.

它为尝试安装包的用户提供了有用且与发行版相关的信息。或者,可以在同一文件中提供翻译

Error-de_DE=Wenn ist das Nunstück git und Slotermeyer?

 Ja! Beiherhund das Oder die Virtualenvironment gersput!

在某些情况下,例如在创建后不会更新的单应用程序容器映像中,分发商可以选择不提供 EXTERNALLY-MANAGED 文件,以便用户可以安装他们喜欢的任何内容(就像他们今天可以做的那样),而无需手动覆盖此规则。

仅写入目标 sysconfig 方案#

通常,Python 包安装程序安装到 sysconfig 标准库包返回的方案中的目录。通常,这是 sysconfig.get_default_scheme() 返回的方案,但根据配置(例如 pip install --user),它可能会使用不同的方案。

每当安装程序安装到 sysconfig 方案时,此规范声明安装程序绝不应修改或删除该方案之外的文件。例如,如果它正在升级包,并且该包已安装在该方案之外的目录中(可能在另一个方案的目录中),则它应保留现有文件。

如果安装程序最终在升级期间对现有安装进行影子,我们建议它在其运行结束时生成一个警告。

如果安装程序安装到 sysconfig 方案之外的位置(例如,pip install --target),则此小节不适用。

发行版的建议#

本节是非规范性的。它提供了我们认为发行版应遵循的最佳实践,除非它们有其他具体原因。

将安装标记为外部管理#

发行版应在其 stdlib 目录中创建 EXTERNALLY-MANAGED 文件。

引导用户使用虚拟环境#

该文件应包含一条有用且与发行版相关的错误消息,既说明如何通过发行版的包管理器安装系统级包,又说明如何设置虚拟环境。如果用户经常在 python3 命令可用(尤其是在 pipget-pip 可用)但 python3 -m venv 无法正常工作的情况下使用发行版,则该消息应清楚地说明如何使 python3 -m venv 正常工作。

考虑打包 pipx,这是一个用于安装 Python 语言应用程序的工具,并在错误中建议使用它。pipx 会自动为该应用程序单独创建一个虚拟环境,对于希望安装一些 Python 语言软件(发行版中不可用)但本身不是 Python 用户的最终用户来说,这是一个更好的默认设置。在发行版中打包 pipx 可以避免让用户 pip install --user --break-system-packages pipx避免破坏系统包的尴尬局面。考虑安排好发行版的 Python 包/环境,以便最终用户(例如,Fedora 上的 python3 或 Debian 上的 python3-full)依赖于 pipx。

在容器映像中保留标记文件#

为单应用程序容器(例如,Docker 容器映像)生成官方映像的发行版应保留 EXTERNALLY-MANAGED 文件,最好采用一种方式,即使该映像的用户在其映像内安装了包更新,它也不会消失(例如,RUN apt-get dist-upgrade)。

创建单独的发行版和本地目录#

发行版应在系统解释器的 sys.path 上放置两个单独的路径,一个用于发行版安装的包,另一个用于本地系统管理员安装的包,并将 sysconfig.get_default_scheme() 配置为指向后一个路径。这可确保 pip 等工具不会修改发行版安装的包。本地系统管理员的路径应在 sys.path 上的发行版路径之前,以便本地安装优先于发行版包。

例如,Fedora 和 Debian(及其衍生产品)都通过使用 /usr/local(用于本地安装的包)和 /usr(用于发行版安装的包)来实现此拆分。Fedora 使用 /usr/local/lib/python3.x/site-packages/usr/lib/python3.x/site-packages。(Debian 使用 /usr/local/lib/python3/dist-packages/usr/lib/python3/dist-packages 作为本地编译的 Python 解释器的另一层分离:如果你在 /usr/local/bin 中构建并安装上游 CPython,它将查找 /usr/local/lib/python3/site-packages,而 Debian 希望确保通过本地构建的解释器安装的包不会出现在发行版解释器的 sys.path 上。)

请注意,/usr/local/usr 的分割类似于 PATH 环境变量通常包含 /usr/local/bin:/usr/bin,并且非发行版软件默认安装到 /usr/local。此分割是 文件系统层次结构标准 推荐的。

有两种方法可以做到这一点。一种是,如果您直接构建和打包 Python 库(例如,您的打包助手解压一个轮子或调用 setup.py install),安排这些工具使用不在 sysconfig 方案中但仍位于 sys.path 中的目录。

另一种方法是安排在包构建中运行时更改默认 sysconfig 方案,而不是在已安装系统上运行时更改。来自 bpo-43976sysconfig 自定义挂钩应该使这变得容易(一旦接受并实现):让您的打包工具设置环境变量或其他可检测的配置,并定义一个 get_preferred_schemes 函数,以便在从包构建内部调用时返回一个不同的方案。然后,您可以将 pip install 作为发行版打包的一部分使用。

我们建议添加一个 --scheme=... 选项来指示 pip 在特定方案下运行。(有关 pip 目前如何确定方案,请参见下面的 实施说明。)一旦该选项可用,对于本地测试和实际打包,您将能够运行类似 pip install --scheme=posix_distro 的命令,以将包明确安装到发行版的位置(绕过 get_preferred_schemes)。如果绝对需要,还可以使用 pip uninstall --scheme=posix_distro 来使用 pip 从系统管理的目录中删除包。

要使用 pip 安装包,您还需要禁止 EXTERNALLY-MANAGED 标记文件以允许 pip 运行,或在命令行中覆盖它。您可能希望在构建 chroot 中使用与在容器映像中相同的禁止标记文件的方法。

将这些设置成自动(在构建环境中禁止标记文件,并让 get_preferred_schemes 自动返回发行版的方案)的优点是,一个未装饰的 pip install 将在包构建中工作,这通常意味着一个未经修改的上游构建脚本碰巧在内部调用 pip install 将执行正确操作。当然,您可以确保您的打包过程始终调用 pip install --scheme=posix_distro --break-system-packages,这也将起作用。

此处最佳方法在很大程度上取决于发行版的惯例和打包机制。

类似地,不是用于可导入 Python 代码的 sysconfig 路径 - 即 includeplatincludescriptsdata - 也应该有两个变体,一个供发行版打包的软件使用,另一个供本地安装的软件使用,并且发行版应该设置成两者都可用。例如,一个典型的符合 FHS 的发行版将使用 /usr/local/include 作为默认方案的 include,使用 /usr/include 作为发行版打包的标头,并将两者都放在编译器的搜索路径上,它将使用 /usr/local/bin 作为默认方案的 scripts,使用 /usr/bin 作为发行版打包的入口点,并将两者都放在 $PATH 上。

实施说明#

本节是非规范性的,包含与规范和潜在实施相关的说明。

目前(截至 2021 年 5 月),pip 没有直接公开一种选择目标 sysconfig 方案的方法,但它有三种在安装时查找方案的方法

pip install

调用 sysconfig.get_default_scheme(),通常(在上游 CPython 和大多数当前发行版中)与 get_preferred_scheme('prefix') 相同。

pip install --prefix=/some/path

调用 sysconfig.get_preferred_scheme('prefix')

pip install --user

调用 sysconfig.get_preferred_scheme('user')

最后,pip install --target=/some/path 直接写入 /some/path,而无需查找任何方案。

Debian 目前携带一个 补丁来更改虚拟环境中的默认安装位置,使用一些启发式(包括检查 VIRTUAL_ENV 环境变量),很大程度上是为了使虚拟环境中使用的目录保持 site-packages 而不是 dist-packages。这不会特别影响此提案,因为该补丁的实现实际上并没有更改默认 sysconfig 方案,特别是没有更改 sysconfig.get_path("stdlib") 的结果。

Fedora 目前携带一个 补丁来更改不在 rpmbuild 中运行时的默认安装位置,他们使用它来实现两个系统范围的目录方法。从概念上讲,这是 bpo-43976 设想的那种钩子,只不过作为 distutils 的代码补丁实现,而不是作为更改的 sysconfig 方案实现。

上面 is_virtual_environment 的实现,以及加载 EXTERNALLY-MANAGED 文件并从中查找错误消息的逻辑,也可以添加到标准库(分别为 syssysconfig),以集中它们的实现,但它们不必立即添加。

历史记录#

  • 2022 年 6 月:此规范已通过 PEP 668 批准。