4.6 与 pip 和 pip-tools 的兼容性

原文链接: https://docs.astral.sh/uv/pip/compatibility/

4.6 与 pip 和 pip-tools 的兼容性

uv 被设计为常用 pip 和 pip-tools 工作流的即插即用替代品。

非正式地说,其意图是让现有的 pip 和 pip-tools 用户无需对打包工作流做实质性改动即可切换到 uv;在大多数情况下,把 pip install 换成 uv pip install 应“直接可用”。

不过 uv 并不打算成为 pip 的精确克隆,你越偏离常见的 pip 工作流,就越可能遇到行为差异。某些情况下这些差异是已知且有意为之的;另一些情况下可能源于实现细节;还有一些情况下可能是缺陷。

本文档列出 uv 与 pip 之间已知的差异,并给出理由、变通方案,以及对未来兼容性的意图说明。

配置文件与环境变量

uv 不读取 pip 专属的配置文件或环境变量,例如 pip.conf 或 PIP_INDEX_URL。

读取为其他工具准备的配置文件和环境变量有许多缺点:

  1. 这要求与被读取工具做到逐缺陷兼容,因为用户最终会依赖格式、解析器等方面的缺陷。
  2. 如果目标工具以某种方式改变了格式,uv 就被绑死在以等价方式随之改变上。
  3. 如果该配置以某种方式做了版本管理,uv 就需要知道用户期望使用目标工具的哪个版本。
  4. 这会阻止 uv 引入目标工具中不存在的任何设置或配置,否则 pip.conf(或类似文件)就不再能用于 pip。
  5. 它可能让用户困惑,因为 uv 会读取实际并不影响其行为的设置,而许多用户可能不期望 uv 读取为其他工具准备的配置文件。

作为替代,uv 支持自己的环境变量,例如 UV_INDEX_URL。uv 也支持在 uv.toml 文件中或 pyproject.toml 的 [tool.uv.pip] 段落中做持久配置。更多信息请参阅配置文件。

预发布版本兼容性

默认情况下(if-necessary),uv 优先使用稳定版本而不是预发布版本,只有当所有满足当前约束的稳定候选都在解析过程中被拒绝时,才回退到预发布版本。

例如,假设只有 c==1.0 和 c==2.0a1 可用。c>=1 与 a -> c>=0.5a1 合起来允许这两个版本,但无论先发现哪个 requirement,uv 都会选择 c==1.0。如果另一个生效的 requirement 拒绝了 c==1.0,uv 会回退到 c==2.0a1。

使用 --prerelease allow 可以为每个包考虑预发布版本而不优先稳定候选,使用 --prerelease disallow 则完全排除它们。

explicit 模式只对包含预发布标识的第一方 requirement 考虑预发布版本(优先稳定版本,仅在必要时回退到预发布),而对其他所有包禁用预发布版本。

注意

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

预发布版本众所周知很难建模,因为依赖要求在解析过程中是逐步发现的。uv 为每个包保持固定的候选全集,并先尝试稳定候选再尝试预发布版本,因此回溯可以到达预发布版本,而不会使 PubGrub 已学到的不兼容性失效。

同时存在于多个索引上的包

在 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,例如:

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

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

传递 URL 依赖

虽然 uv 对 URL 依赖(例如 ruff @ https://...)提供一等支持,但它在两处与 pip 在处理传递 URL 依赖时不同。

第一,uv 假定非 URL 依赖不会把 URL 依赖引入解析。换句话说,它假定从仓库获取的依赖本身不依赖 URL。如果非 URL 依赖确实引入了 URL 依赖,uv 会在解析期间拒绝该 URL 依赖。(注意 PyPI 不允许已发布的包依赖 URL 依赖;其他仓库可能更宽松。)

第二,如果某个约束(--constraint)或覆盖(--override)是使用直接 URL 依赖定义的,而被约束的包自身又有直接 URL 依赖,那么在输入 requirement 集合中没有其他地方引用该 URL 时,uv 可能在解析期间拒绝该传递直接 URL 依赖。

如果 uv 拒绝了某个传递 URL 依赖,最好的做法是把该 URL 依赖作为直接依赖写进相关的 pyproject.toml 或 requirement.in 文件,因为上述约束不适用于直接依赖。

默认使用虚拟环境

uv pip install 和 uv pip sync 被设计为默认与虚拟环境配合工作。

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

这与 pip 不同:如果没有激活的虚拟环境,pip 会把包安装到全局环境,而且不会搜索未激活的虚拟环境。

在 uv 中,你可以通过 --python /path/to/python 选项提供 Python 可执行文件路径,或通过 --system 标志(它会像 pip 那样安装到 PATH 上找到的第一个 Python 解释器)安装到非虚拟环境。

换句话说,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
# 该文件由 uv 通过以下命令自动生成:
#    uv pip compile requirements.in
annotated-types==0.6.0
    # 来自 pydantic
anyio==4.3.0
    # 来自 starlette
fastapi==0.1.17
idna==3.6
    # 来自 anyio
pydantic==2.6.3
    # 来自 fastapi
pydantic-core==2.16.3
    # 来自 pydantic
sniffio==1.3.1
    # 来自 anyio
starlette==0.37.2
    # 来自 fastapi
typing-extensions==4.10.0
    # 来自
    #   pydantic
    #   pydantic-core

反之,如果某个解析器优先包含最新版本的 fastapi,它就需要使用一个满足上界的旧版 starlette。实际上这需要回退到 starlette==0.36.3:

# requirements.txt
# 该文件由 uv 通过以下命令自动生成:
#    uv pip compile requirements.in
annotated-types==0.6.0
    # 来自 pydantic
anyio==4.3.0
    # 来自 starlette
fastapi==0.110.0
idna==3.6
    # 来自 anyio
pydantic==2.6.3
    # 来自 fastapi
pydantic-core==2.16.3
    # 来自 pydantic
sniffio==1.3.1
    # 来自 anyio
starlette==0.36.3
    # 来自 fastapi
typing-extensions==4.10.0
    # 来自
    #   fastapi
    #   pydantic
    #   pydantic-core

当 uv 的解析与 pip 的差异不可接受时,往往说明说明符过于宽松,用户应考虑收紧它们。例如在 starlette 与 fastapi 的例子中,用户可以要求 fastapi>=0.110.0。

pip check

目前 uv pip check 会给出以下诊断:

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

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

--user 与 user 安装方案

uv 不支持 --user 标志(它基于 user 安装方案安装包)。我们建议改用虚拟环境来隔离包安装。

此外,如果 pip 检测到用户对目标目录没有写权限(在某些系统上安装到系统 Python 时就是如此),它会回退到 user 安装方案。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 无法预先推断包名,它会构建给定 URL 上的源码分发,因为在这种情况下 uv 不构建其元数据就无法判断该包是否“被允许”。

pip 和 uv 都允许在提供 --only-binary 时构建和安装可编辑 requirement。例如 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)兼容:

1
2
3
4
5
6
7
8
9
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__ 目录)。要在安装期间启用字节码编译,请给 uv pip install 或 uv pip sync 传入 --compile-bytecode 标志,或把 UV_COMPILE_BYTECODE 环境变量设为 1。

跳过字节码编译在某些工作流中可能不可取;例如我们建议在 Docker 构建中启用字节码编译以改善启动时间(代价是构建时间增加)。

由于字节码编译会抑制 Python 解释器发出的各种警告,极少数情况下,运行用 uv 安装的 Python 代码时你可能看到 SyntaxWarning 或 DeprecationWarning 消息,而使用 pip 时不会出现。这些都是有效警告,只是通常被字节码编译过程隐藏,可以忽略、在上游修复,或通过在 uv 中启用字节码编译同样抑制它们。

严格程度与规范执行

uv 往往比 pip 更严格,经常会拒绝 pip 会安装的包。例如 uv 会拒绝包含无效 URL 片段的 HTML 索引(参见 PEP 503),而 pip 会忽略这类片段。

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

如果 uv 因规范违规拒绝了 pip 会安装的包,最好的做法是先尝试安装该包的更新版本;如果仍然失败,再向包维护者报告该问题。

pip 命令行选项与子命令

uv 不支持 pip 命令行选项和子命令的完整集合,但支持其中很大一部分。

缺失的选项和子命令会根据用户需求和实现复杂度排定优先级,通常由各自的 issue 跟踪。例如:

如果你遇到缺失的选项或子命令,请在 issue 跟踪器中搜索是否已被报告;如果没有,可以考虑新建一个 issue。欢迎给已有的 issue 点赞以表达你的关注。

仓库身份验证

uv 不支持 pip 为 --keyring-provider 提供的 auto 或 import 选项。目前只支持 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 list 和 uv pip freeze 列出它们,并用 uv pip uninstall 卸载它们。

构建约束

通过 --constraint(或 UV_CONSTRAINT)提供约束时,uv 不会在解析构建依赖(即构建源码分发)时应用这些约束。构建约束应改通过专门的 --build-constraint(或 UV_BUILD_CONSTRAINT)设置提供。

而 pip 在通过 PIP_CONSTRAINT 指定时会把约束应用于构建依赖,但通过命令行上的 --constraint 提供时不会。

例如,要确保所有以 setuptools 为构建依赖的包都用 setuptools 60.0.0 构建,请使用 --build-constraint 而不是 --constraint。

pip compile 的默认值

pip compile 与 pip-tools 的默认行为之间有几处不大但值得注意的差异。

默认情况下,uv 不会把编译后的 requirements 写入输出文件。uv 要求用户通过 -o 或 --output-file 选项显式指定输出文件。

默认情况下,uv 在输出编译后的 requirements 时会剥离 extras。也就是说 uv 默认相当于 --strip-extras,而 pip-compile 默认相当于 --no-strip-extras。pip-compile 计划在下一个主版本(v8.0.0)中改变该默认值,届时两个工具都将默认使用 --strip-extras。要在 uv 中保留 extras,请给 uv pip compile 传入 --no-strip-extras 标志。

默认情况下,uv 不会把任何索引 URL 写入输出文件,而 pip-compile 会输出任何与默认索引(PyPI)不匹配的 --index-url 或 --extra-index-url。要在输出文件中包含索引 URL,请给 uv pip compile 传入 --emit-index-url 标志。与 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 输入文件中 requirement 的顺序。

Wheel 文件名与元数据校验

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

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

包名规范化

默认情况下,uv 会把包名规范化为其 PEP 503 兼容形式,并在所有输出场景中使用这些规范化后的名称。这与 pip 不同,pip 倾向于保留仓库上发布的原始包名。

例如 uv pip list 显示规范化后的包名(例如 docstring-parser),而 pip list 显示未规范化的包名(例如 docstring_parser):

1
2
3
4
5
6
7
8
9
(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
最后修改 September 25, 2026: 更新 (221c74c33)