外部管理环境¶
虽然有些 Python 安装完全由安装 Python 的用户管理,但其他安装可能由其他方式提供和管理(例如 Linux 发行版中的操作系统包管理器,或应用程序中带有专用安装程序的捆绑 Python 环境)。
尝试使用传统的 Python 打包工具来操作此类环境,往好里说可能令人困惑,往坏里说可能彻底破坏整个底层操作系统。文档和互操作性指南在解决此类问题上作用有限。
本规范定义了一个 EXTERNALLY-MANAGED 标记文件,允许 Python 安装指示像 pip 这样的 Python 特定工具,它们既不安装也不删除包到解释器的默认安装环境,而应该引导最终用户使用Python 虚拟环境。
它还标准化了 sysconfig 方案的解释,这样,如果一个 Python 特定包管理器将要在解释器范围的上下文中安装一个包,它可以以一种避免与外部包管理器冲突的方式进行,并降低破坏外部包管理器附带的软件的风险。
术语¶
本规范中使用的几个术语在它所涵盖的上下文中具有多种含义。为清晰起见,本规范以特定方式使用以下术语:
- 发行版 (distro)
“distribution”的缩写,指各种软件的集合,理想情况下设计为协同工作,包括(与本文相关的上下文中)Python 解释器本身、用 Python 编写的软件以及用其他语言编写的软件。也就是说,这是在“Linux distro”或“Berkeley Software Distribution”等短语中使用的含义。
一个发行版可以是一个操作系统 (OS),例如 Debian、Fedora 或 FreeBSD。它也可以是一个安装在现有操作系统之上的叠加发行版,例如 Homebrew 或 MacPorts。
本文使用短语“distro”,因为术语“distribution”在 Python 打包上下文中还有另一个含义:单个 Python 语言软件的源或二进制发行包,即
setuptools.dist.Distribution或“sdist”的含义。为了避免混淆,本文根本不使用“distribution”这个简单术语。在 Python 打包意义上,它使用完整短语“distribution package”或简称“package”(见下文)。发行版的提供者——收集、发布软件并进行任何必要修改的团队或公司——是它的 distributor。
- 包 (package)
可以在 Python 中安装和使用的软件单元。也就是说,这指的是 Python 特定的打包工具通常所称的发行包(distribution package)或简称为“distribution”;“package”这个口语缩写在 Python Package Index 的意义上使用。
本文不使用“package”来指代包含 Python 模块的可导入名称,尽管在许多情况下,一个发行包由一个同名的可导入包组成。
本文通常不使用“package”来指代发行版包管理器(例如
.deb或.rpm文件)的安装单元。在需要时,它会使用诸如“a distro’s package”之类的措辞。(同样,在许多情况下,Python 包会作为发行版包的一部分提供,例如名为python-加上 Python 包名称的包。)- Python 特定包管理器
以符合 Python 打包标准的方式安装、升级和/或删除 Python 包的工具。最流行的 Python 特定包管理器是 pip;其他示例包括旧的 Easy Install 命令以及直接使用
setup.py命令。(请注意,
easy_install命令已在 setuptools 版本 52 中删除,该版本于 2021 年 1 月 23 日发布。)(Conda 是一个特殊情况,因为
conda命令不仅可以安装 Python 包,因此在某些意义上它更像一个发行版包管理器。由于conda命令通常只在 Conda 创建的环境中操作,因此当conda充当 Python 特定包管理器时,本文中的大多数问题都不适用于它。)- 发行版包管理器
一个用于在已安装的发行版实例中安装、升级和/或删除发行版包的工具,它能够安装 Python 包以及非 Python 包,因此通常有自己独立的已安装软件数据库,与已安装发行版数据库无关。示例包括
apt、dpkg、dnf、rpm、pacman和brew。显著的特点是,如果一个包是由发行版包管理器安装的,那么以满足 Python 特定包管理器的方式删除或升级它通常会使发行版包管理器处于不一致的状态。本文在某些上下文中也使用“外部包管理器”或“系统包管理器”等短语来指代发行版包管理器。
- 遮蔽 (shadow)
遮蔽已安装的 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 上下文之前,它默认应进行以下检查:
它是否在虚拟环境之外运行?它可以通过
sys.prefix == sys.base_prefix来判断。在
sysconfig.get_path("stdlib", sysconfig.get_default_scheme())标识的目录中是否存在EXTERNALLY-MANAGED文件?
如果这两个条件都为真,安装程序应退出并显示错误消息,指示在此 Python 解释器目录中禁用虚拟环境之外的包安装。
安装程序应提供一种用户覆盖这些规则的方式,例如命令行标志 --break-system-packages。此选项不应默认启用,并且应暗示其使用存在风险。
EXTERNALLY-MANAGED 文件是一个 INI 风格的元数据文件,旨在可由标准库 configparser 模块解析。如果文件可以使用 configparser.ConfigParser(interpolation=None) 以 UTF-8 编码解析,并且包含一个 [externally-managed] 部分,则安装程序应查找文件中指定的错误消息并将其作为错误的一部分输出。如果 locale.getlocale(locale.LC_MESSAGES) 返回元组的第一个元素(即语言代码)不是 None,它应该查找名为 Error- 后面跟着语言代码的键的值作为错误消息。如果该键不存在,并且语言代码包含下划线或连字符,它应该查找名为 Error- 后面跟着语言代码中下划线或连字符之前部分的键。如果找不到其中任何一个,或者语言代码是 None,它应该查找简单名为 Error 的键。
如果安装程序在文件中找不到错误消息(可能是因为文件无法解析,或者没有合适的错误键),那么安装程序应只使用其自己的预定义错误消息,该消息应建议用户创建虚拟环境来安装包。
在其 Python 包的 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 命令可用(特别是 pip 或 get-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。这种分离是文件系统层次标准 (Filesystem Hierarchy Standard) 推荐的。
您可以通过两种方式实现这一点。一种是,如果您直接构建和打包 Python 库(例如,您的打包助手解压 wheel 或调用 setup.py install),请安排这些工具使用不在 sysconfig 方案中但仍在 sys.path 上的目录。
另一种方法是安排默认的 sysconfig 方案在包构建内部运行时与在已安装系统上运行时不同。bpo-43976 中的 sysconfig 自定义钩子应该会使这变得容易(一旦被接受并实现):让您的打包工具设置一个环境变量或一些其他可检测的配置,并定义一个 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 路径——即 include、platinclude、scripts 和 data——也应该有两种变体,一种用于发行版打包的软件,一种用于本地安装的软件,并且发行版应配置为两者都可用。例如,一个典型的符合 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 文件并从中查找错误消息的逻辑,也可能添加到标准库中(分别为 sys 和 sysconfig),以集中其实现,但它们目前不需要添加。
版权¶
本文档置于公共领域或 CC0-1.0-Universal 许可下,以更宽松者为准。
历史¶
2022 年 6 月:本规范已通过PEP 668 批准。