跳到内容

pippip-tools 的兼容性

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

非正式地说,其目的是让现有的 pippip-tools 用户可以在不对打包工作流进行重大更改的情况下切换到 uv;在大多数情况下,将 pip install 替换为 uv pip install 应该能够“直接生效”。

然而,uv 并不旨在成为 pip完全克隆,你偏离通用 pip 工作流越远,就越有可能遇到行为上的差异。在某些情况下,这些差异可能是已知的且有意的;在另一些情况下,它们可能是实现细节的结果;还有一些情况可能是 bug。

本文概述了 uv 与 pip 之间的已知差异,以及基本原理、变通方法和未来兼容性的意图说明。

配置文件和环境变量

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

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

  1. 它需要与目标工具保持“逐个 bug”的兼容性,因为用户最终会依赖于格式、解析器等中的 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 会尊重传递性依赖项中的预发布标识符,并且如果没有任何稳定版本符合依赖项要求,则允许传递性依赖项使用预发布版本。

注意

在 pip 26.0 之前,这种行为并不一致。

预发布版本极其难以建模,并且是打包工具中 bug 的常见来源。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)。

如果包因缺少构建时依赖项而无法安装,请尝试使用该包的较新版本;如果问题仍然存在,请考虑向包维护者提交 issue,请求他们更新打包配置以声明正确的 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 依赖项,最好的做法是在相关的 pyproject.tomlrequirement.in 文件中将该 URL 依赖项作为直接依赖项提供,因为上述约束不适用于直接依赖项。

默认使用虚拟环境

uv pip installuv pip sync 默认设计用于虚拟环境。

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

这与 pip 不同,后者如果未激活虚拟环境,会将包安装到全局环境中,并且不会搜索未激活的虚拟环境。

在 uv 中,你可以通过 --python /path/to/python 选项提供 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 版本,它将需要使用较旧的 fastapi 版本,该版本排除了对 starlette 的上限。在实践中,这需要回退到 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 无法确定该包是否“被允许”。

pip 和 uv 都允许在提供 --only-binary 时构建和安装可编辑需求。例如,允许 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 的完整命令行选项和子命令集合,尽管它支持其中的大部分。

缺失的选项和子命令根据用户需求和实现的复杂性进行优先级排序,并且通常在各个 issue 中进行跟踪。例如

如果你遇到缺失的选项或子命令,请搜索 issue 跟踪器查看是否已报告,如果没有,请考虑提交一个新的 issue。欢迎点赞任何现有的 issue 以表达你的兴趣。

注册表身份验证

uv 不支持 pip--keyring-providerautoimport 选项。目前,仅支持 subprocess 选项。

pip 不同,uv 默认不启用 keyring 身份验证。

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 在通过 PIP_CONSTRAINT 指定时会将约束应用于构建依赖项,但在命令行中通过 --constraint 提供时则不会。

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

pip compile 默认值

pip compilepip-tools 的默认行为有一些细微但值得注意的差异。

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

默认情况下,uv 在输出编译后的需求时会剥离额外功能(extras)。换句话说,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 说明符

在根据 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 输入文件中需求的顺序。

Wheel 文件名和元数据验证

默认情况下,uv 会拒绝文件名与文件内部 wheel 元数据不一致的 wheel。例如,名为 foo-1.0.0-py3-none-any.whl 的 wheel,如果其中包含的元数据表明版本为 1.0.1,则会被 uv 拒绝,但会被 pip 接受。

要强制 uv 接受此类 wheel,请在环境中设置 UV_SKIP_WHEEL_FILENAME_CHECK=1

包名称标准化

默认情况下,uv 会将包名称标准化以匹配其 符合 PEP 503 的形式,并在所有输出上下文中使用这些标准化名称。这与 pip 不同,pip 通常会保留在注册表上发布时的原始包名称。

例如,uv pip list 显示标准化的包名称(例如 docstring-parser),而 pip list 显示非标准化的包名称(例如 docstring_parser

(venv) $ diff --side-by-side  <(pip list) <(uv pip list)
Package          Version                    Package          Version
---------------- -------                    ---------------- -------
docstring_parser 0.16                         | docstring-parser 0.16
jaraco.classes   3.4.0                        | jaraco-classes   3.4.0
more-itertools   10.7.0                         more-itertools   10.7.0
pip              25.1                           pip              25.1
PyMuPDFb         1.24.10                      | pymupdfb         1.24.10
PyPDF2           3.0.1                        | pypdf2           3.0.1