跳到内容

pippip-tools 的兼容性

uv 被设计为常见 pippip-tools 工作流程的替代品。

非正式地,我们的意图是让现有的 pippip-tools 用户可以切换到 uv,而无需对他们的打包工作流程进行有意义的更改;并且,在大多数情况下,将 pip install 替换为 uv pip install 应该“只是工作”。

但是,uv 旨在成为 pip完全 克隆,并且您越偏离常见的 pip 工作流程,就越有可能遇到行为差异。在某些情况下,这些差异可能是已知且有意为之的;在其他情况下,它们可能是实现细节的结果;在另一些情况下,它们可能是错误。

本文档概述了 uv 和 pip 之间的已知差异,以及基本原理、解决方法以及未来兼容性的声明。

配置文件和环境变量

uv 不读取特定于 pip 的配置文件或环境变量,例如 pip.confPIP_INDEX_URL

读取用于其他工具的配置文件和环境变量存在许多缺点

  1. 它需要与目标工具的 bug-for-bug 兼容性,因为用户最终会依赖于格式、解析器等中的错误。
  2. 如果目标工具以某种方式更改格式,则 uv 会被锁定为以等效方式更改它。
  3. 如果该配置以某种方式进行版本控制,则 uv 需要知道用户期望使用的目标工具的哪个版本
  4. 它阻止 uv 引入目标工具中不存在的任何设置或配置,否则 pip.conf(或类似文件)将不再可与 pip 一起使用。
  5. 这可能会导致用户混淆,因为 uv 会读取实际上不会影响其行为的设置,并且许多用户可能期望 uv 读取用于其他工具的配置文件。

相反,uv 支持自己的环境变量,例如 UV_INDEX_URL。 uv 还支持 uv.toml 文件或 pyproject.toml[tool.uv.pip] 部分中的持久配置。有关更多信息,请参见配置文件

预发布版本兼容性

默认情况下,在以下两种情况下,uv 将在依赖关系解析期间接受预发布版本

  1. 如果该包是直接依赖项,并且其版本标记包括预发布说明符(例如,flask>=2.0.0rc1)。
  2. 如果某个包的所有已发布版本都是预发布版本。

如果依赖关系解析由于传递预发布版本而失败,uv 将提示用户使用 --prerelease allow 重新运行,以允许所有依赖项使用预发布版本。

或者,您可以在 requirements.in 文件中使用预发布说明符(例如,flask>=2.0.0rc1)添加传递依赖项,以选择支持该特定依赖项的预发布版本。

总而言之,uv 需要预先知道解析器是否应接受给定包的预发布版本。同时,根据解析器遇到相关说明符的顺序,pip可能会尊重传递依赖项中的预发布标识符(#1641)。

预发布版本 出了名的难以 建模,并且是打包工具中错误的常见来源。即使被视为参考实现的 pip,也存在许多关于预发布版本处理的未解决问题(#12469, #12470, #40505, etc.)。 uv 的预发布版本处理是 有意 限制的,并且 有意 需要用户选择预发布版本,以确保正确性。

将来,uv 可能 支持传递依赖项中的预发布标识符。但是,这很可能取决于 Python 打包规范的发展。 现有的 PEP 未涵盖“依赖关系解析”,而是侧重于 单个 版本说明符的行为。因此,关于打包生态系统中预发布版本的正确和预期行为存在未解决的问题。

存在于多个索引上的包

在 uv 和 pip 中,用户都可以指定多个包索引,从中搜索给定包的可用版本。但是,uv 和 pip 在处理存在于多个索引上的包的方式上有所不同。

例如,假设一家公司在私有索引 (--extra-index-url) 上发布了内部版本的 requests,但也允许默认从 PyPI 安装包。在这种情况下,私有 requests 将与 PyPI 上的公共 requests 冲突。

当 uv 在多个索引中搜索包时,它将按顺序迭代索引(优先使用 --extra-index-url 而不是默认索引),并在找到匹配项后立即停止搜索。这意味着如果一个包存在于多个索引中,uv 将将其候选版本限制为包含该包的第一个索引中存在的版本。

同时,pip 将合并来自所有索引的候选版本,并从合并的集合中选择最佳版本,但它 不能保证 搜索索引的顺序,并且期望包在名称和版本方面是唯一的,即使在索引之间也是如此。

uv 的行为使得如果一个包存在于内部索引中,则应始终从内部索引安装,而绝不从 PyPI 安装。这样做的目的是为了防止“依赖混淆”攻击,即攻击者在 PyPI 上发布一个恶意包,该包与内部包的名称相同,从而导致安装恶意包而不是内部包。例如,请参阅 2022 年 12 月的 torchtriton 攻击

从 v0.1.39 开始,用户可以通过 --index-strategy 命令行选项或 UV_INDEX_STRATEGY 环境变量选择 pip 样式的多索引行为,该变量支持以下值

  • first-index(默认):在所有索引中搜索每个包,将候选版本限制为包含该包的第一个索引中存在的版本,优先使用 --extra-index-url 索引而不是默认索引 URL。
  • unsafe-first-match:在所有索引中搜索每个包,但优先使用具有兼容版本的第一个索引,即使其他索引上存在较新版本也是如此。
  • unsafe-best-match:在所有索引中搜索每个包,并从候选版本的合并集中选择最佳版本。

虽然 unsafe-best-match 最接近 pip 的行为,但它使用户面临“依赖混淆”攻击的风险。

uv 还支持将包固定到专用索引(参见:索引),以便始终从特定索引安装给定包。

PEP 517 构建隔离

默认情况下,uv 使用 PEP 517 构建隔离(类似于 pip install --use-pep517),遵循 pypa/build 并预期 pip 将来会默认使用 PEP 517 构建(pypa/pip#9175)。

如果由于缺少构建时依赖项而导致包安装失败,请尝试使用较新版本的包;如果问题仍然存在,请考虑向包维护者提出问题,要求他们更新打包设置以声明正确的 PEP 517 构建时依赖项。

作为一种紧急措施,您可以预先安装包的构建依赖项,然后使用 --no-build-isolation 运行 uv pip install,如下所示

uv pip install wheel && uv pip install --no-build-isolation biopython==1.77

有关已知在 PEP 517 构建隔离下失败的包的列表,请参阅 #2252

传递 URL 依赖项

虽然 uv 包括对 URL 依赖项的一流支持(例如,ruff @ https://...),但它在处理传递 URL 依赖项方面与 pip 不同,具体体现在以下两个方面。

首先,uv 假设非 URL 依赖项不会将 URL 依赖项引入解析中。换句话说,它假设从注册表中提取的依赖项本身不依赖于 URL。如果非 URL 依赖项确实引入了 URL 依赖项,uv 将在解析期间拒绝该 URL 依赖项。(请注意,PyPI 不允许已发布的包依赖于 URL 依赖项;其他注册表可能更宽松。)

其次,如果使用直接 URL 依赖项定义约束 (--constraint) 或覆盖 (--override),并且受约束的包本身具有直接 URL 依赖项,则如果该 URL 未在输入要求的集合中其他地方引用,uv 可能 会在解析期间拒绝该传递直接 URL 依赖项。

如果 uv 拒绝了传递 URL 依赖项,则最佳做法是将 URL 依赖项作为相关 pyproject.tomlrequirement.in 文件中的直接依赖项提供,因为上述约束不适用于直接依赖项。

默认虚拟环境

uv pip installuv pip sync 旨在默认与虚拟环境一起使用。

具体来说,uv 始终会将包安装到当前活动的虚拟环境中,或者在当前目录或任何父目录中搜索名为 .venv 的虚拟环境(即使它未激活)。

这与 pip 不同,如果没有活动的虚拟环境,pip 会将包安装到全局环境中,并且不会搜索非活动虚拟环境。

在 uv 中,您可以通过 --python /path/to/python 选项或通过 --system 标志安装到非虚拟环境中,该标志会将包安装到 PATH 中找到的第一个 Python 解释器中,就像 pip 一样。

换句话说,uv 反转了默认设置,需要明确选择安装到系统 Python 中,这可能会导致损坏和其他复杂情况,应仅在有限的情况下进行。

有关更多信息,请参见“使用任意 Python 环境”

解析策略

对于给定的依赖项说明符集,通常情况下,没有一个“正确”的包安装集。相反,存在许多满足说明符的有效包集。

pip 和 uv 均不对将要安装的包的确切集合做任何保证;仅保证解析将是一致的、确定性的并且符合说明符。因此,在某些情况下,pip 和 uv 将产生不同的解析;但是,两种解析同样有效。

例如,考虑

requirements.in
starlette
fastapi

在撰写本文时,最新的 starlette 版本是 0.37.2,最新的 fastapi 版本是 0.110.0。但是,fastapi==0.110.0 也依赖于 starlette,并引入了上限:starlette>=0.36.3,<0.37.0

如果解析器优先包含最新版本的 starlette,则需要使用不包括 starlette 上限的旧版本 fastapi。实际上,这需要回退到 fastapi==0.1.17

requirements.txt
# This file was autogenerated by uv via the following command:
#    uv pip compile requirements.in
annotated-types==0.6.0
    # via pydantic
anyio==4.3.0
    # via starlette
fastapi==0.1.17
idna==3.6
    # via anyio
pydantic==2.6.3
    # via fastapi
pydantic-core==2.16.3
    # via pydantic
sniffio==1.3.1
    # via anyio
starlette==0.37.2
    # via fastapi
typing-extensions==4.10.0
    # via
    #   pydantic
    #   pydantic-core

或者,如果解析器优先包含最新版本的 fastapi,则需要使用满足上限的旧版本 starlette。实际上,这需要回退到 starlette==0.36.3

requirements.txt
# This file was autogenerated by uv via the following command:
#    uv pip compile requirements.in
annotated-types==0.6.0
    # via pydantic
anyio==4.3.0
    # via starlette
fastapi==0.110.0
idna==3.6
    # via anyio
pydantic==2.6.3
    # via fastapi
pydantic-core==2.16.3
    # via pydantic
sniffio==1.3.1
    # via anyio
starlette==0.36.3
    # via fastapi
typing-extensions==4.10.0
    # via
    #   fastapi
    #   pydantic
    #   pydantic-core

当 uv 解析与 pip 的解析以不良方式不同时,这通常表明说明符太松散,并且用户应考虑收紧它们。例如,在 starlettefastapi 的情况下,用户可以要求 fastapi>=0.110.0

pip check

目前,uv pip check 将显示以下诊断信息

  • 一个包没有 METADATA 文件,或者无法解析 METADATA 文件。
  • 一个包有一个 Requires-Python,它与正在运行的解释器的 Python 版本不匹配。
  • 一个包依赖于一个未安装的包。
  • 一个包依赖于一个已安装的包,但版本不兼容。
  • 多个版本的包安装在虚拟环境中。

在某些情况下,uv pip check 将显示 pip check 不显示的诊断信息,反之亦然。例如,与 uv pip check 不同,当多个版本的包安装在当前环境中时,pip check会发出警告。

--useruser 安装方案

uv 不支持 --user 标志,该标志根据 user 安装方案安装包。相反,我们建议使用虚拟环境来隔离包安装。

此外,如果 pip 检测到用户没有目标目录的写入权限,它将回退到 user 安装方案,这在某些系统上安装到系统 Python 时就是这种情况。 uv 不实现任何此类回退。

有关更多信息,请参见 #2077

--only-binary 强制执行

--only-binary 参数用于将安装限制为预构建的二进制发行版。当提供 --only-binary :all: 时,pip 和 uv 都将拒绝从 PyPI 和其他注册表构建源代码发行版。

但是,当依赖项作为直接 URL 提供时(例如,uv pip install https://...),pip 强制执行 --only-binary,并且将为所有此类包构建源代码发行版。

同时,uv 确实 为直接 URL 依赖项强制执行 --only-binary,但有一个例外:给定 uv pip install https://... --only-binary flask,如果 uv 无法提前推断出包名称,uv 构建给定 URL 处的源代码发行版,因为在这种情况下 uv 无法确定包是否“允许”,而无需构建其元数据。

即使提供了 --only-binary,pip 和 uv 都允许构建和安装可编辑的要求。例如,允许 uv pip install -e . --only-binary :all:

--no-binary 强制执行

--no-binary 参数用于将安装限制为源代码发行版。当提供 --no-binary 时,uv 将拒绝安装预构建的二进制发行版,但重用本地缓存中已存在的任何二进制发行版。

此外,与 pip 相比,在提供 --no-binary 时,uv 的解析器仍将从预构建的二进制发行版读取元数据。

manylinux_compatible 强制执行

PEP 600 描述了一种机制,通过该机制,Python 发行者可以通过在 _manylinux 标准库模块上定义 manylinux_compatible 函数来选择退出 manylinux 兼容性。

uv 尊重 manylinux_compatible,但仅针对当前的 glibc 版本进行测试,并将 manylinux_compatible 的返回值全局应用。

换句话说,如果 manylinux_compatible 返回 True,uv 将系统视为 manylinux 兼容;如果它返回 False,uv 将系统视为 manylinux 不兼容,而不会为每个 glibc 版本调用 manylinux_compatible

此方法不是规范的完整实现,但与常见的全面 manylinux_compatible 实现(例如 no-manylinux)兼容

from __future__ import annotations
manylinux1_compatible = False
manylinux2010_compatible = False
manylinux2014_compatible = False


def manylinux_compatible(*_, **__):  # PEP 600
    return False

字节码编译

pip 不同,默认情况下,uv 不会在安装期间将 .py 文件编译为 .pyc 文件(即,uv 不会创建或填充 __pycache__ 目录)。 要在安装期间启用字节码编译,请将 --compile-bytecode 标志传递给 uv pip installuv pip sync,或将 UV_COMPILE_BYTECODE 环境变量设置为 1

在工作流程中跳过字节码编译可能是不希望的;例如,我们建议在 Docker 构建中启用字节码编译,以缩短启动时间(以增加构建时间为代价)。

由于字节码编译会抑制 Python 解释器发出的各种警告,因此在极少数情况下,您可能会在运行使用 uv 安装的 Python 代码时看到 SyntaxWarningDeprecationWarning 消息,而使用 pip 时不会出现这些消息。 这些是有效的警告,但通常会被字节码编译过程隐藏,并且可以忽略、在上游修复或通过在 uv 中启用字节码编译来类似地抑制。

严格性和规范执行

uv 往往比 pip 更严格,并且通常会拒绝 pip 会安装的包。 例如,uv 拒绝具有无效 URL 片段的 HTML 索引(参见:PEP 503),而 pip 将忽略此类片段。

在某些情况下,uv 会为已知存在特定规范合规性问题的流行包实现宽松行为。

如果 uv 由于规范违规而拒绝了 pip 会安装的包,则最佳做法是首先尝试安装较新版本的包;如果失败,则将问题报告给包维护者。

pip 命令行选项和子命令

uv 不支持 pip 的完整命令行选项和子命令集,尽管它确实支持一个很大的子集。

缺少选项和子命令的优先级基于用户需求和实现的复杂性,并且倾向于在单独的问题中进行跟踪。 例如

如果您遇到缺少的选项或子命令,请搜索问题跟踪器以查看是否已报告,如果没有,请考虑打开一个新问题。 请随意支持任何现有问题以表达您的兴趣。

注册表身份验证

uv 不支持 pipautoimport 选项来指定 --keyring-provider。 目前,仅支持 subprocess 选项。

pip 不同,默认情况下 uv 不启用密钥环身份验证。

pip 不同,uv 不会等到请求返回 HTTP 401 才搜索身份验证。 uv 会将身份验证附加到所有具有可用凭据的主机的请求。

egg 支持

uv 不支持在 pip 中被认为是遗留或已弃用的功能。 例如,uv 不支持 .egg 样式的发行版。

但是,uv 确实对 (1) .egg-info 样式的发行版(偶尔在 Docker 镜像和 Conda 环境中找到)和 (2) 遗留的可编辑 .egg-link 样式的发行版具有部分支持。

具体来说,uv 不支持安装新的 .egg-info.egg-link 样式的发行版,但将在解析期间尊重任何此类现有发行版,使用 uv pip listuv pip freeze 列出它们,并使用 uv pip uninstall 卸载它们。

构建约束

当通过 --constraint(或 UV_CONSTRAINT)提供约束时,uv 在解析构建依赖项时会应用约束(即,构建源代码发行版)。 相反,应通过专用的 --build-constraint(或 UV_BUILD_CONSTRAINT)设置提供构建约束。

同时,当通过 PIP_CONSTRAINT 指定时,pip 会将约束应用于构建依赖项,但当通过命令行上的 --constraint 提供时则不会。

例如,要确保使用 setuptools 60.0.0 来构建任何具有对 setuptools 的构建依赖项的包,请使用 --build-constraint,而不是 --constraint

pip compile 默认值

pip compilepip-tools 的默认行为中存在一些小的但值得注意的差异。

默认情况下,uv 不会将编译后的要求写入输出文件。 相反,uv 要求用户使用 -o--output-file 选项显式指定输出文件。

默认情况下,uv 在输出编译后的要求时会剥离额外内容。 换句话说,uv 默认为 --strip-extras,而 pip-compile 默认为 --no-strip-extraspip-compile 计划在下一个主要版本 (v8.0.0) 中更改此默认值,届时这两个工具都将默认为 --strip-extras。 要使用 uv 保留额外内容,请将 --no-strip-extras 标志传递给 uv pip compile

默认情况下,uv 不会将任何索引 URL 写入输出文件,而 pip-compile 会输出任何与默认值 (PyPI) 不匹配的 --index-url--extra-index-url。 要在输出文件中包含索引 URL,请将 --emit-index-url 标志传递给 uv pip compile。 与 pip-compile 不同,当传递 --emit-index-url 时,uv 将包含所有索引 URL,包括默认索引 URL。

requires-python 强制执行

在评估依赖项的 requires-python 范围时,uv 仅考虑下限,而完全忽略上限。 例如,>=3.8, <4 被视为 >=3.8。 尊重 requires-python 的上限通常会导致形式上正确但实际上不正确的解析,例如,解析器将回溯到省略上限的第一个已发布版本(参见:Requires-Python 上限)。

当针对 requires-python 说明符评估 Python 版本时,uv 会将候选版本截断为主要、次要和补丁组件,忽略(例如)预发布和发布后标识符。

例如,声明 requires-python: >=3.13 的项目将接受 Python 3.13.0b1。 虽然 3.13.0b1 并不严格大于 3.13,但当省略预发布标识符时,它大于 3.13。

虽然这并不严格符合 PEP 440,但它确实pip 一致。

包优先级

给定一组要求,通常有许多可能的解决方案,解析器必须在它们之间进行选择。 uv 的解析器和 pip 的解析器具有不同的包优先级集。 虽然两个解析器都将用户提供的顺序用作其优先级之一,但 pip 具有 uv 没有的其他 优先级。 因此,uv 比 pip 更容易受到用户顺序更改的影响。

例如,uv pip install foo bar 优先考虑较新版本的 foo 而不是 bar,并且可能导致与 uv pip install bar foo 不同的解析。 同样,此行为适用于 uv pip compile 的输入文件中要求的排序。