PEP 405 – Python 虚拟环境
- 作者:
- Carl Meyer <carl at oddbird.net>
- BDFL 委托:
- Alyssa Coghlan
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 主题:
- 打包
- 创建日期:
- 2011年6月13日
- Python 版本:
- 3.3
- 发布历史:
- 2011年10月24日,2011年10月28日,2012年3月6日,2012年5月24日
- 决议:
- Python-Dev 消息
摘要
本 PEP 提议为 Python 添加一种机制,用于创建轻量级的“虚拟环境”,它们拥有自己的 site 目录,并可选择与系统 site 目录隔离。每个虚拟环境都有自己的 Python 二进制文件(允许创建具有不同 Python 版本的环境),并且可以在其 site 目录中拥有自己独立安装的 Python 包集,但与基础安装的 Python 共享标准库。
动机
Python 虚拟环境的实用性已经通过现有第三方虚拟环境工具的流行得到了充分证实,其中最主要的是 Ian Bicking 的 virtualenv。虚拟环境已被广泛用于依赖管理和隔离、无需系统管理员权限即可轻松安装和使用 Python 包、以及跨多个 Python 版本自动化测试 Python 软件等用途。
现有的虚拟环境工具缺乏 Python 自身行为的支持。像 rvirtualenv 这样不将 Python 二进制文件复制到虚拟环境中的工具,无法提供与系统 site 目录可靠的隔离。而 virtualenv 则复制了 Python 二进制文件,这迫使它不得不复制 Python site 模块的大部分内容,并手动符号链接/复制不断变化的标准库模块集到虚拟环境中,以便在每次启动时执行微妙的引导过程。(Virtualenv 必须复制二进制文件才能提供隔离,因为 Python 在搜索 sys.prefix 之前会解引用符号链接的可执行文件)。
PYTHONHOME 环境变量是 Python 唯一的现有内置虚拟环境解决方案,它要求将整个标准库复制/符号链接到每个环境中。复制整个标准库不是一个轻量级的解决方案,并且跨平台对符号链接的支持仍然不一致(即使在支持符号链接的 Windows 平台上,创建它们也常常需要管理员权限)。
与 Python 集成并借鉴现有第三方工具多年经验的虚拟环境机制可以降低维护成本、提高可靠性,并更容易地提供给所有 Python 用户。
规范
当 Python 二进制文件执行时,它会尝试确定其前缀(存储在 sys.prefix 中),然后该前缀用于查找标准库和其他关键文件,并由 site 模块确定 site-package 目录的位置。目前,前缀的查找(假设未设置 PYTHONHOME)首先是向上遍历文件系统树,查找表示标准库存在的标记文件(os.py),如果未找到,则回退到二进制文件中硬编码的构建时前缀。
本 PEP 提议在此搜索中增加一个新步骤。如果 Python 可执行文件旁边或其上一级目录中(如果可执行文件是符号链接,则不解引用)找到 pyvenv.cfg 文件,则扫描此文件以查找形如 key = value 的行。如果找到 home 键,则表示该 Python 二进制文件属于虚拟环境,并且 home 键的值是用于创建此虚拟环境的 Python 可执行文件所在的目录。
在这种情况下,前缀查找继续正常进行,使用 home 键的值作为有效的 Python 二进制文件位置,这将找到基础安装的前缀。sys.base_prefix 被设置为此值,而 sys.prefix 被设置为包含 pyvenv.cfg 的目录。
(如果未找到 pyvenv.cfg 或其中不包含 home 键,前缀查找将正常进行,sys.prefix 将等于 sys.base_prefix。)
此外,还添加了 sys.base_exec_prefix,并以类似方式处理 sys.exec_prefix。(sys.exec_prefix 等同于 sys.prefix,但用于平台特定文件;默认情况下,它与 sys.prefix 具有相同的值。)
修改了标准库模块 site 和 sysconfig,以便标准库和头文件相对于 sys.base_prefix / sys.base_exec_prefix 查找,而 site-package 目录(用 sysconfig 术语来说是“purelib”和“platlib”)仍然相对于 sys.prefix / sys.exec_prefix 查找。
因此,一个最简单的 Python 虚拟环境将只包含 Python 二进制文件的一个副本或符号链接,以及一个 pyvenv.cfg 文件和一个 site-packages 目录。
与系统 site-packages 隔离
默认情况下,虚拟环境与系统级 site-packages 目录完全隔离。
如果 pyvenv.cfg 文件还包含一个键 include-system-site-packages,其值为 true(不区分大小写),则 site 模块也会在虚拟环境 site 目录之后将系统 site 目录添加到 sys.path 中。这样,系统安装的包仍然可以导入,但虚拟环境中安装的同名包将优先。
PEP 370 用户级别的 site-packages 被视为 venv 的系统 site-packages 的一部分:它们不能从隔离的 venv 访问,但可以从 include-system-site-packages = true 的 venv 访问。
创建虚拟环境
本 PEP 还提议在标准库中添加一个新的 venv 模块,用于实现虚拟环境的创建。此模块可以使用 -m 标志执行
python3 -m venv /path/to/new/virtual/environment
还提供了一个 pyvenv 安装脚本,以使其更方便
pyvenv /path/to/new/virtual/environment
运行此命令会创建目标目录(如果父目录不存在则创建),并在其中放置一个 pyvenv.cfg 文件,其中 home 键指向运行该命令的 Python 安装。它还会创建一个 bin/(在 Windows 上是 Scripts)子目录,其中包含 python3 可执行文件的副本(或符号链接),以及来自 packaging 标准库模块的 pysetup3 脚本(以方便将 PyPI 中的包轻松安装到新的 venv 中)。它还会创建一个(最初为空的)lib/pythonX.Y/site-packages(在 Windows 上是 Lib\site-packages)子目录。
如果目标目录已存在,则会引发错误,除非提供了 --clear 选项,在这种情况下,目标目录将被删除,虚拟环境创建将照常进行。
创建的 pyvenv.cfg 文件还包含 include-system-site-packages 键,如果 pyvenv 运行带有 --system-site-packages 选项,则设置为 true,默认情况下为 false。
可以为 pyvenv 提供多个路径,在这种情况下,将根据给定选项在每个提供的路径上创建相同的 venv。
venv 模块还在 venv 的 bin 或 Scripts 目录中放置了适用于 POSIX 和 Windows 系统的“shell 激活脚本”。这些脚本只是将虚拟环境的 bin(或 Scripts)目录添加到用户 shell PATH 的前面。这对于使用虚拟环境并非严格必要(因为可以同样使用 venv 的 python 二进制文件或脚本的显式路径),但它很方便。
为了允许 pysetup 和其他 Python 包管理器以与安装到正常 Python 安装相同的方式将包安装到虚拟环境中,并避免在 sysconfig 中对虚拟环境进行特殊处理(除了在适当情况下使用 sys.base_prefix 代替 sys.prefix),虚拟环境的内部布局模仿了 Python 安装本身在每个平台上的布局。因此,POSIX 系统上的典型虚拟环境布局将是
pyvenv.cfg
bin/python3
bin/python
bin/pysetup3
include/
lib/python3.3/site-packages/
而在 Windows 系统上
pyvenv.cfg
Scripts/python.exe
Scripts/python3.dll
Scripts/pysetup3.exe
Scripts/pysetup3-script.py
... other DLLs and pyds...
Include/
Lib/site-packages/
安装到虚拟环境中的第三方包的 Python 模块将放置在 site-packages 目录中,可执行文件将放置在 bin/ 或 Scripts 中。
注意
在正常的 Windows 系统级安装中,Python 二进制文件本身不会放在“Scripts/”子目录中,而默认的 venv 布局则会。这在虚拟环境中很有用,因为用户只需将一个目录添加到其 shell PATH 中即可有效地“激活”虚拟环境。
注意
在 Windows 上,还需要将 DLL 和 pyd 文件从已编译的标准库模块复制或符号链接到环境中,因为如果 venv 是从非系统范围的 Python 安装创建的,当 Python 从 venv 运行时,Windows 将无法找到 Python 安装的这些文件的副本。
Sysconfig 安装方案和用户 site
这种方法明确选择不为 venvs 引入新的 sysconfig 安装方案。相反,通过修改 sys.prefix,我们确保基于 sys.prefix 的现有安装方案将在 venv 中正常工作。安装到其他安装方案(例如,用户 site 方案),其路径不相对于 sys.prefix,将完全不受 venv 的影响。
基于虚拟特定 sysconfig 方案创建 Python 虚拟环境的替代实现可能是可行的,但其健壮性会较差,因为它需要更多的代码来识别它是否在虚拟环境中运行。
复制与符号链接
本 PEP 中的技术对于复制或符号链接的 Python 二进制文件(以及 Windows 上需要的其他 DLL)同样适用。在可能的情况下,符号链接是更优的选择,因为在底层 Python 安装升级的情况下,venv 中复制的 Python 可执行文件可能会与已安装的标准库不同步,需要手动升级。
符号链接存在一些跨平台困难
- 并非所有 Windows 版本都支持符号链接,即使支持,创建它们也常常需要管理员权限。
- 在 OS X 框架构建的 Python 中,sys.executable 只是一个执行真实 Python 二进制文件的存根。符号链接此存根不起作用;它必须被复制。(幸运的是,存根也很小,并且不会因 Python 的错误修复升级而更改,因此复制它不是问题)。
因此,本 PEP 建议在除 Windows 和 OS X 框架构建之外的所有平台上符号链接二进制文件。如果具有适当的权限,可以使用 --symlink 选项强制在支持符号链接的 Windows 版本上使用符号链接。(此选项对 OS X 框架构建无效,因为符号链接在那里永远无法工作,并且没有优点)。
在 Windows 上,如果未使用 --symlink,这意味着如果底层 Python 安装升级,venv 中的 Python 二进制文件和 DLL 应更新,否则可能与升级后的标准库不匹配。pyvenv 脚本接受 --upgrade 选项,以便在现有 venv 上轻松执行此升级。
包含文件
当前的 virtualenv 以这种方式处理包含文件
在 POSIX 系统上,如果已安装的 Python 的包含文件位于 ${base_prefix}/include/pythonX.X,virtualenv 会创建 ${venv}/include/ 并将 ${base_prefix}/include/pythonX.X 符号链接到 ${venv}/include/pythonX.X。在 Windows 上,Python 的包含文件位于 {{ sys.prefix }}/Include,且符号链接不可靠,virtualenv 会将 {{ sys.prefix }}/Include 复制到 ${venv}/Include。这确保了在虚拟环境中构建和安装的扩展模块将始终在相对于 sys.prefix 的预期位置找到它们所需的 Python 头文件。
当扩展模块安装其自己的头文件时,此解决方案并不理想,因为这些头文件的默认安装位置可能是指向系统目录的符号链接,而该目录可能不可写。一个安装程序 pip 明确通过将头文件安装到非标准位置 ${venv}/include/site/pythonX.X/ 来解决此问题,因为 Python 中目前没有针对 site 特定包含目录的标准抽象。
本 PEP 提出了一个略有不同的方法,尽管其效果和优缺点基本相同。我们不将包含文件符号链接或复制到 venv 中,而是简单地修改 sysconfig 方案,以便始终相对于 base_prefix 而不是 prefix 查找头文件。(我们还在 venv 中创建一个 include/ 目录,以便安装程序可以在环境中放置安装的包含文件)。
在 distutils/packaging 中更好地处理包含文件,以及由此延伸到 pyvenv,可能是一个值得未来独立 PEP 的领域。目前,我们提出 virtualenv 的行为在实践中已被证明至少是“足够好”的。
API
上述高级方法利用了一个简单的 API,该 API 提供了一些机制,供第三方虚拟环境创建者根据自身需求定制环境创建。
venv 模块包含一个 EnvBuilder 类,该类在实例化时接受以下关键字参数
system_site_packages- 一个布尔值,指示系统 Python site-packages 是否应可用于环境。默认为False。clear- 一个布尔值,如果为 true,将删除任何现有目标目录,而不是引发异常。默认为False。symlinks- 一个布尔值,指示是否尝试符号链接 Python 二进制文件(以及任何必要的 DLL 或其他二进制文件,例如pythonw.exe),而不是复制。默认为False。
实例化的 env-builder 有一个 create 方法,该方法接受一个必需参数,即目标目录的路径(绝对路径或相对于当前目录),该目录将包含虚拟环境。create 方法要么在指定目录中创建环境,要么引发适当的异常。
venv 模块还提供一个模块级别的 create 函数作为便利
def create(env_dir,
system_site_packages=False, clear=False, use_symlinks=False):
builder = EnvBuilder(
system_site_packages=system_site_packages,
clear=clear,
use_symlinks=use_symlinks)
builder.create(env_dir)
第三方虚拟环境工具的创建者可以自由地使用提供的 EnvBuilder 类作为基类。
EnvBuilder 类的 create 方法展示了可用于定制的钩子
def create(self, env_dir):
"""
Create a virtualized Python environment in a directory.
:param env_dir: The target directory to create an environment in.
"""
env_dir = os.path.abspath(env_dir)
context = self.create_directories(env_dir)
self.create_configuration(context)
self.setup_python(context)
self.post_setup(context)
可以重写 create_directories、create_configuration、setup_python 和 post_setup 等方法。这些方法的功能是
create_directories- 创建环境目录和所有必要的目录,并返回一个上下文对象。这只是一个属性(例如路径)的持有者,供其他方法使用。create_configuration- 在环境中创建pyvenv.cfg配置文件。setup_python- 在环境中创建 Python 可执行文件(以及在 Windows 下的 DLL)的副本。post_setup- 一个(默认无操作的)钩子方法,可以在第三方子类中重写,用于在虚拟环境中预安装包或安装脚本。
此外,EnvBuilder 提供了一个实用方法,可以在子类的 post_setup 中调用,以协助在虚拟环境中安装自定义脚本。install_scripts 方法接受 context 对象(见上文)和一个目录路径作为参数。该目录应包含子目录“common”、“posix”、“nt”,每个子目录都包含要放置在环境 bin 目录中的脚本。“common”和与 os.name 对应的目录中的内容在进行一些占位符文本替换后被复制
__VENV_DIR__被替换为环境目录的绝对路径。__VENV_NAME__被替换为环境名称(环境目录的最终路径段)。__VENV_BIN_NAME__被替换为 bin 目录的名称(要么是bin,要么是Scripts)。__VENV_PYTHON__被替换为环境可执行文件的绝对路径。
参考实现中的 DistributeEnvBuilder 子类说明了如何实际使用自定义钩子将 Distribute 预安装到虚拟环境中。预计 DistributeEnvBuilder 不会真正添加到 Python 核心中,但它使参考实现对于测试和探索目的更直接有用。
向后兼容性
拆分 sys.prefix 的含义
任何遵循此思路的虚拟环境工具(旨在隔离 site-packages,同时仍利用基础 Python 的标准库,而无需将其符号链接到虚拟环境中)都提出了对目前都包含在 sys.prefix 中的两个不同含义(以及其他含义)的拆分:即“标准库在哪里?”和“第三方模块应安装到 site-packages 的哪个位置?”这两个问题的答案。
这种拆分可以通过为前者或后者前缀引入新的 sys 属性来处理。任何一种选择都可能导致与假设 sys.prefix 具有其他含义的软件存在一些向后不兼容性。(此类软件最好使用 site 和 sysconfig 模块中的 API 来回答这些问题,而不是直接使用 sys.prefix,在这种情况下不存在向后兼容性问题,但实际上 sys.prefix 有时会被使用。)
sys.prefix 的文档将其描述为“一个字符串,表示安装平台无关的 Python 文件的 site-specific 目录前缀”,并明确提到标准库和头文件位于 sys.prefix 下。它没有提及 site-packages。
维护此文档定义的含义意味着将 sys.prefix 指向基础系统安装(即标准库和头文件所在的位置),并在 sys 中引入一个新值(例如 sys.site_prefix)以指向 site-packages 的前缀。这将保持 sys.prefix 的文档语义,但如果第三方代码使用 sys.prefix 而不是 sys.site_prefix 或适当的 site API 来查找 site-packages 目录,则存在破坏隔离的风险。
最值得注意的案例可能是 setuptools 及其分支 distribute,它们主要使用 distutils 和 sysconfig API,但确实直接使用 sys.prefix 来构建 site 目录列表,以便在预检查中确定 pth 文件可以有用地放置在哪里。
此外,Google Code Search 显示,使用 sys.prefix 构建 site-packages 路径的包和使用它(例如)从代码执行跟踪中消除标准库的包的使用情况大致各占一半。
尽管需要修改 sys.prefix 的文档定义,但本 PEP 倾向于让 sys.prefix 指向虚拟环境(site-packages 所在的位置),并引入 sys.base_prefix 指向标准库和 Python 头文件。此选择的理由是
- 倾向于在更大程度的虚拟环境隔离方面犯错。
- Virtualenv 已经修改了
sys.prefix以指向虚拟环境,实际上这并没有成为问题。 - 无需修改 setuptools/distribute。
对其他 Python 实现的影响
本 PEP 的大部分更改发生在标准库中,该库由其他 Python 实现共享,不应引起任何问题。
其他 Python 实现需要复制解释器引导程序新的 sys.prefix 查找行为,包括定位和解析 pyvenv.cfg 文件(如果存在)。
参考实现
参考实现可在 CPython Mercurial 仓库的克隆中找到。要测试它,请构建并运行 bin/pyvenv /path/to/new/venv 以创建虚拟环境。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0405.rst
最后修改时间:2025-02-01 08:59:27 GMT