与 pip 和 pip-tools 的兼容性
uv 被设计为常见 pip 和 pip-tools 工作流的直接替代品。
非正式地说,其目的是让现有的 pip 和 pip-tools 用户可以在不对打包工作流进行重大更改的情况下切换到 uv;在大多数情况下,将 pip install 替换为 uv pip install 应该能够“直接生效”。
然而,uv 并不旨在成为 pip 的完全克隆,你偏离通用 pip 工作流越远,就越有可能遇到行为上的差异。在某些情况下,这些差异可能是已知的且有意的;在另一些情况下,它们可能是实现细节的结果;还有一些情况可能是 bug。
本文概述了 uv 与 pip 之间的已知差异,以及基本原理、变通方法和未来兼容性的意图说明。
配置文件和环境变量
uv 不会读取 pip 特有的配置文件或环境变量,例如 pip.conf 或 PIP_INDEX_URL。
读取旨在用于其他工具的配置文件和环境变量有许多缺点
- 它需要与目标工具保持“逐个 bug”的兼容性,因为用户最终会依赖于格式、解析器等中的 bug。
- 如果目标工具以某种方式更改了格式,uv 则被迫以等效方式进行更改。
- 如果该配置以某种方式进行了版本控制,uv 将需要知道用户期望使用目标工具的哪个版本。
- 它阻碍了 uv 引入任何在目标工具中不存在的设置或配置,因为否则
pip.conf(或类似文件)将无法再与pip一起使用。 - 它会导致用户困惑,因为 uv 会读取实际上不影响其行为的设置,而且许多用户可能不期望 uv 读取旨在用于其他工具的配置文件。
相反,uv 支持自己的环境变量,例如 UV_INDEX_URL。uv 还支持在 uv.toml 文件或 pyproject.toml 的 [tool.uv.pip] 部分中进行持久化配置。更多信息,请参见 配置文件。
预发布版本的兼容性
默认情况下,uv 会在以下两种情况下的依赖解析过程中接受预发布版本
- 如果该包是直接依赖项,并且其版本标记包含预发布说明符(例如
flask>=2.0.0rc1)。 - 如果包的所有已发布版本都是预发布版本。
如果由于传递性预发布版本导致依赖解析失败,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,如下所示
有关已知在 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.toml 或 requirement.in 文件中将该 URL 依赖项作为直接依赖项提供,因为上述约束不适用于直接依赖项。
默认使用虚拟环境
uv pip install 和 uv pip sync 默认设计用于虚拟环境。
具体来说,uv 将始终将包安装到当前活动的虚拟环境中,或者搜索当前目录或任何父目录中名为 .venv 的虚拟环境(即使它未激活)。
这与 pip 不同,后者如果未激活虚拟环境,会将包安装到全局环境中,并且不会搜索未激活的虚拟环境。
在 uv 中,你可以通过 --python /path/to/python 选项提供 Python 可执行文件的路径,或通过 --system 标志安装到非虚拟环境中,该标志会将包安装到 PATH 中找到的第一个 Python 解释器中,就像 pip 一样。
换句话说,uv 颠倒了默认设置,要求显式选择加入才能安装到系统 Python 中,这可能会导致损坏和其他复杂情况,并且应仅在有限的情况下进行。
更多信息,请参见 "使用任意 Python 环境"。
解析策略
对于给定的依赖说明符集合,通常没有单一的“正确”安装包集合。相反,有许多满足这些说明符的有效包集合。
pip 和 uv 都不保证将安装精确的包集合;只保证解析是一致的、确定性的且符合说明符。因此,在某些情况下,pip 和 uv 会产生不同的解析结果;但是,这两种解析结果应该同样有效。
例如,考虑
在撰写本文时,最新的 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
# 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
# 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 不同时,这通常表明说明符过于宽松,用户应该考虑收紧它们。例如,在 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 检测到用户对目标目录没有写入权限,它将回退到 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 install 或 uv pip sync,或将 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 60.0.0 来构建任何对 setuptools 有构建依赖的包,请使用 --build-constraint,而不是 --constraint。
pip compile 默认值
pip compile 和 pip-tools 的默认行为有一些细微但值得注意的差异。
默认情况下,uv 不会将编译后的需求写入输出文件。相反,uv 要求用户使用 -o 或 --output-file 选项显式指定输出文件。
默认情况下,uv 在输出编译后的需求时会剥离额外功能(extras)。换句话说,uv 默认执行 --strip-extras,而 pip-compile 默认执行 --no-strip-extras。pip-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