3.16 缓存

原文链接: https://docs.astral.sh/uv/concepts/cache/

3.16 缓存

依赖缓存

uv 使用积极缓存,以避免重新下载(和重新构建)此前运行中已访问过的依赖。

uv 缓存语义的具体细节取决于依赖的性质:

  • 对于仓库依赖(例如从 PyPI 下载的那些),uv 遵循 HTTP 缓存头。
  • 对于直接 URL 依赖,uv 遵循 HTTP 缓存头,同时还会根据 URL 本身缓存。
  • 对于 Git 依赖,uv 根据完全解析出的 Git 提交哈希缓存。因此 uv pip compile 在写出解析后的依赖集合时,会把 Git 依赖固定到特定提交哈希。
  • 对于本地依赖,uv 根据源归档(即本地 .whl 或 .tar.gz 文件)的最后修改时间缓存。对于目录,uv 根据 pyproject.toml、setup.py 或 setup.cfg 文件的最后修改时间缓存。
  • 对于扁平索引(即 --find-links 位置),uv 假定索引内容不可变,按名称缓存每个文件。因此,用新内容替换同名文件(例如把重新构建的 wheel 放入 --find-links 目录)在缓存刷新之前不会被识别。

如果你遇到缓存问题,uv 提供了一些应急手段:

  • 要完全清空缓存,请运行 uv cache clean。要清空特定包的缓存,请运行 uv cache clean <package-name>。例如 uv cache clean ruff 会清空 ruff 包的缓存。
  • 要强制 uv 重新校验所有依赖的缓存数据,请给任何命令传入 --refresh(例如 uv sync --refresh 或 uv pip install --refresh ...)。
  • 要强制 uv 重新校验特定依赖的缓存数据,请给任何命令传入 --refresh-package(例如 uv sync --refresh-package ruff 或 uv pip install --refresh-package ruff ...)。
  • 要强制 uv 忽略已有的已安装版本,请给任何安装命令传入 --reinstall(例如 uv sync --reinstall 或 uv pip install --reinstall ...)。(可以考虑先运行 uv cache clean <package-name>,以确保在重装前清空缓存。)

作为一种特例,对于在命令行中显式传入的任何本地目录依赖(例如 uv pip install .),uv 始终会重新构建并重装。

动态元数据

默认情况下,uv 只会在目录根中的 pyproject.toml、setup.py 或 setup.cfg 文件发生变化,或新增/删除 src 目录时,才重新构建并重装本地目录依赖(例如可编辑安装)。这是一种启发式规则,某些情况下可能导致比预期更少的重装。

要把额外信息纳入某个包的缓存键,可以在 tool.uv.cache-keys 下添加缓存键条目,它同时覆盖文件路径和 Git 提交哈希。设置 tool.uv.cache-keys 会替换默认值,因此任何必要的文件(如 pyproject.toml)仍应包含在用户定义的缓存键中。

例如,如果某个项目在 pyproject.toml 中指定依赖,但使用 setuptools-scm 管理版本,因而在提交哈希或依赖变化时都应重新构建,你可以在项目的 pyproject.toml 中加入:

1
2
3
# pyproject.toml
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true } }]

如果你的动态元数据纳入了 Git 标签集合的信息,可以把缓存键扩展为包含标签:

1
2
3
# pyproject.toml
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true } }]

同样,如果某个项目读取 requirements.txt 来填充依赖,你可以在项目的 pyproject.toml 中加入:

1
2
3
# pyproject.toml
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { file = "requirements.txt" }]

file 键支持 glob,语法遵循 glob crate。例如,要在项目目录或其任何子目录中的 .toml 文件被修改时使缓存失效,请使用:

1
2
3
# pyproject.toml
[tool.uv]
cache-keys = [{ file = "**/*.toml" }]

注意

使用 glob 可能代价较高,因为 uv 可能需要遍历文件系统来判断任何文件是否发生变化。这又可能需要遍历庞大或嵌套很深的目录。

同样,如果某个项目依赖环境变量,你可以在项目的 pyproject.toml 中加入下面内容,以在该环境变量变化时使缓存失效:

1
2
3
# pyproject.toml
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { env = "MY_ENV_VAR" }]

最后,要在特定目录(如 src)被创建或删除时使项目失效,请在项目的 pyproject.toml 中加入:

1
2
3
# pyproject.toml
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { dir = "src" }]

注意 dir 键只会跟踪目录本身的增删,而不会跟踪目录内的任意变化。

作为一种应急手段,如果某个项目使用了 tool.uv.cache-keys 未覆盖的 dynamic 元数据,你可以把该项目加入 tool.uv.reinstall-package 列表,指示 uv 始终重新构建并重装它:

1
2
3
# pyproject.toml
[tool.uv]
reinstall-package = ["my-package"]

这会强制 uv 在每次运行时都重新构建并重装 my-package,无论该包的 pyproject.toml、setup.py 或 setup.cfg 文件是否变化。

缓存安全性

即使针对同一个虚拟环境,并发运行多个 uv 命令也是安全的。uv 的缓存设计为线程安全且只追加,因此对多个并发读写者都很健壮。安装时 uv 会对目标虚拟环境加基于文件的锁,以避免跨进程的并发修改。

注意绝不应直接修改缓存(例如删除某个文件或目录)。

清理缓存

uv 提供了几种从缓存中移除条目的机制:

  • uv cache clean 移除缓存目录中的所有缓存条目,把它完全清空。
  • uv cache clean ruff 移除 ruff 包的所有缓存条目,用于使单个或有限几个包的缓存失效。
  • uv cache prune 移除所有未使用的缓存条目以及所有集中管理的项目环境。例如,缓存目录中可能包含在旧 uv 版本中创建、已不再需要因而可以安全移除的条目。集中管理的项目环境会按需重建。定期运行 uv cache prune 是安全的,可以保持缓存目录干净。

默认情况下,缓存清理会估算回收的磁盘空间。启用 cache-physical-space 预览特性可以获得更准确的估算,它会计入硬链接和写时复制克隆:

1
$ uv cache clean --preview-features cache-physical-space

如果某个条目的分配大小无法测量(例如 Btrfs 上的压缩区段),uv 会为其余条目回收的空间报告一个下界。该预览特性目前支持 macOS 和 Linux;其他平台继续报告较粗略的空间回收估算。

在其他 uv 命令运行期间,uv 会阻止修改缓存的操作。默认情况下,那些 uv cache 命令会等待其他 uv 进程终止最多 5 分钟,以避免死锁。该超时可以通过 UV_LOCK_TIMEOUT 更改。在明确知道没有其他 uv 进程在读写信缓存的情况下,可以用 --force 忽略该锁。

在持续集成中缓存

在持续集成环境(例如 GitHub Actions 或 GitLab CI)中缓存包安装产物以加速后续运行很常见。

默认情况下,uv 既缓存它从源码构建的 wheel,也缓存它直接下载的预构建 wheel,以实现高性能的包安装。

不过在持续集成环境中,持久化预构建 wheel 可能并不可取。对 uv 来说,把预构建 wheel 排除在缓存之外(改为每次运行时从仓库重新下载)往往更快。另一方面,缓存从源码构建的 wheel 通常是值得的,因为 wheel 构建过程可能很昂贵,尤其是对扩展模块而言。

为支持这种缓存策略,uv 提供了 uv cache prune --ci 命令,它会从缓存中移除所有预构建 wheel 和已解压的源码分发,但保留任何从源码构建的 wheel。我们建议在持续集成任务结束时运行 uv cache prune --ci,以确保缓存效率最高。示例请参阅 GitHub 集成指南。

缓存目录

uv 按以下顺序确定缓存目录:

  1. 如果请求了 --no-cache,则为临时缓存目录。
  2. 通过 --cache-dir、UV_CACHE_DIR 或 tool.uv.cache-dir 指定的具体缓存目录。
  3. 适合系统的缓存目录,例如 Unix 上的 $XDG_CACHE_HOME/uv 或 $HOME/.cache/uv,Windows 上的 %LOCALAPPDATA%\uv\cache

注意

uv 始终需要一个缓存目录。请求 --no-cache 时,uv 仍会使用临时缓存来在单次调用内共享数据。

大多数情况下应使用 --refresh 而不是 --no-cache —— 前者会为后续操作更新缓存,但不从缓存读取。

缓存目录与 uv 操作的 Python 环境位于同一文件系统对性能很重要。否则 uv 无法把缓存中的文件链接进环境,只能退回到缓慢的复制操作。

缓存版本管理

uv 缓存由若干桶(bucket)组成(例如 wheel 一个桶、源码分发一个桶、Git 仓库一个桶,等等)。每个桶都有版本,因此如果某个发布对缓存格式做了破坏性变更,uv 不会尝试读写不兼容的缓存桶。

例如,uv 0.4.13 对核心元数据桶做了破坏性变更,因此该桶版本从 v12 提升到 v13。在同一缓存版本内,变更保证向前和向后兼容。

由于缓存格式的变更伴随缓存版本的变更,多个 uv 版本可以安全地读写同一个缓存目录。不过,如果给定两个 uv 发布之间的缓存版本发生了变化,那么这些发布可能无法共享相同的底层缓存条目。

例如,uv 0.4.12 与 uv 0.4.13 共用同一个缓存是安全的,不过由于缓存版本变化,缓存本身在核心元数据桶中可能包含重复条目。

最后修改 September 25, 2026: 更新 (221c74c33)