与 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
。
读取其他工具的配置文件和环境变量存在诸多弊端:
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]
部分进行持久化配置。更多信息,请参阅 配置文件。
预发布版本兼容性
默认情况下,uv 在以下两种情况下会在依赖解析时接受预发布版本:
1. 如果该软件包是直接依赖项,并且其版本标记包含预发布说明符(例如 flask>=2.0.0rc1
)。
2. 如果某个软件包的所有已发布版本都是预发布版本。
如果由于传递性预发布版本导致依赖解析失败,uv 会提示用户使用 --prerelease allow
重新运行,以允许所有依赖项使用预发布版本。
或者,你可以将传递性依赖项及其预发布说明符(例如 flask>=2.0.0rc1
)添加到 requirements.in
文件中,以选择对该特定依赖项支持预发布版本。
总之,uv 需要预先知道解析器是否应该接受给定软件包的预发布版本。与此同时,pip
可能 会根据解析器遇到相关说明符的顺序,考虑传递性依赖项中的预发布标识符(#1641)。
预发布版本 极难建模,并且是打包工具中常见的错误来源。即使被视为参考实现的 pip
,在处理预发布版本方面也存在一些未解决的问题(#12469、#12470、#40505 等)。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
,如下所示:
有关已知在 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
不同,pip
在没有激活虚拟环境时会将包安装到全局环境中,并且不会搜索未激活的虚拟环境。
在 uv 中,你可以通过 --python /path/to/python
选项指定 Python 可执行文件的路径,或者使用 --system
标志,将包安装到非虚拟环境中,--system
标志会像 pip
一样,将包安装到 PATH
中找到的第一个 Python 解释器中。
换句话说,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
:
# 此文件由 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
:
# 此文件由 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 在不构建元数据的情况下无法确定该包是否 “允许”。
即使提供了 --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__
目录)。要在安装过程中启用字节码编译,可以在 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
的全部命令行选项和子命令,但支持其中很大一部分。
缺失的选项和子命令会根据用户需求和实现复杂度确定优先级,并且通常会在各个问题中进行跟踪。例如:
- --trusted-host
- --user
如果遇到缺失的选项或子命令,请在问题跟踪器中搜索,查看是否已有相关报告,如果没有,可以考虑新开一个问题。欢迎对现有问题点赞以表明您的关注。
注册中心认证
uv 不支持 pip
中 --keyring-provider
的 auto
或 import
选项。目前仅支持 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 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 在输出编译后的需求时会去除额外项。换句话说,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
说明符评估 Python 版本时,uv 会将候选版本截断为主要版本号、次要版本号和补丁版本号部分,忽略(例如)预发布和发布后标识符。
例如,声明 requires-python: >=3.13
的项目将接受 Python 3.13.0b1。虽然 3.13.0b1 并不严格大于 3.13,但省略预发布标识符后,它大于 3.13。
虽然这并不完全符合 PEP 440,但它与 pip 是一致的。
包优先级
给定一组需求,通常有多种可能的解决方案,解析器必须在它们之间做出选择。uv 的解析器和 pip 的解析器有不同的包优先级设置。虽然两个解析器都将用户提供的顺序作为其优先级之一,但 pip 有 uv 所没有的额外优先级。因此,与 pip 相比,uv 更有可能受到用户顺序变化的影响。
例如,uv pip install foo bar
会优先选择 foo
的较新版本而非 bar
,这可能会导致与 uv pip install bar foo
不同的解析结果。同样,此行为也适用于 uv pip compile
输入文件中需求的排序。