首页
/ uv 项目环境实战:lock 与 sync 的锁定、同步机制全解

uv 项目环境实战:lock 与 sync 的锁定、同步机制全解

2026-09-04 18:26:42作者:董灵辛Dennis

在 uv(一个用 Rust 编写的极速 Python 包与项目管理器)中,"锁定(locking)"负责把项目依赖解析为 uv.lock 锁文件,"同步(syncing)"则负责把锁文件中的一个子集安装进项目环境。本文以官方文档 Locking and syncing 为主线,完整讲解二者的自动触发时机、uv lock / uv sync 的全部关键参数、锁文件新鲜度判定规则、版本升级策略、部分安装与导出格式,并结合 uv 源码印证这些行为的底层实现,帮你把"环境漂移"问题彻底治理好。

一、锁与同步:两个概念的区别

先厘清两个术语(与 依赖解析项目布局 文档配套理解):

  • Locking(锁定):把项目依赖解析为一棵完整的依赖树,并写入锁文件(uv.lock)。
  • Syncing(同步):从锁文件中取出当前环境实际需要的包子集,安装到项目环境(.venv)中。

也就是说,锁文件是"全量依赖图",同步是"按当前选择安装子集",两者的生命周期是解耦的。

二、自动锁定与自动同步

uv 的默认哲学是"自动化优先":

  • 执行 uv run 时,uv 会先对项目执行锁定和同步,再调用你请求的命令,从而保证项目环境始终处于最新状态;
  • 同理,读取锁文件的命令(如 uv tree)也会先自动更新锁文件再执行。

如果需要更严格的控制,有三个标志位可选:

标志 作用
--locked 禁用自动锁定;若锁文件不是最新的,直接报错而不是去更新它
--frozen 完全信任现有锁文件,连"是否最新"的检查都跳过
--no-sync 跳过环境同步,直接执行命令
$ uv run --locked ...      # 锁文件过期则报错
$ uv run --frozen ...      # 不做任何锁文件检查
$ uv run --no-sync ...     # 不做环境同步

从源码可以看到这三种模式是如何区分的。同步入口函数 同时接收 lock_check: LockCheckfrozen: Option<FrozenSource> 两个参数,并在 锁模式判定逻辑 中按优先级映射为 LockMode::Frozen(frozen)、LockMode::Locked(locked/check)、LockMode::DryRun 或默认的 LockMode::Write(正常写入)。而 LockCheck 与 FrozenSource 枚举 还额外追踪了标志来源——来自 CLI、环境变量(如 UV_LOCKED=1UV_FROZEN=1)还是工作区配置(locked / frozen 配置项),出错时能精确提示你是通过什么途径开启的检查。

三、锁文件新鲜度如何判定

uv 判断"锁文件是否过期"的依据是与项目元数据是否一致,核心规则有三条:

  1. pyproject.toml 新增依赖 → 锁文件视为过期;
  2. 修改某依赖的版本约束、使已锁定的版本被排除 → 过期;
  3. 修改版本约束、但已锁定版本仍在约束范围内 → 锁文件仍然视为最新

可以用 uv lock --check 显式检查锁文件是否最新:

$ uv lock --check

该检查等价于其他命令上的 --locked 标志。

重要:发布新版本不会让锁文件过期——uv 不会因为你依赖的某个包发了新版就自动升级它。如需升级,必须显式操作,见升级锁定版本一节。

四、显式创建/更新锁文件

虽然锁文件会自动创建,你也可以显式执行:

$ uv lock

锁文件本身的结构([[package]] 条目、hashes 等)在 项目布局文档 中有详细说明。

五、显式同步环境

同样,环境可以被显式同步:

$ uv sync

手动执行 uv sync 的一个典型场景是:确保你的编辑器(如 IDE 的 Python 解释器)使用的是与锁文件一致的依赖版本,而不是残留的旧版本。

5.1 可编辑安装(Editable)

同步时,uv 会把当前项目(以及其他工作区成员)以 editable 包的形式安装。这样你修改项目源码后无需重新同步,改动就能在环境中即时生效——对开发循环非常关键。

  • 不想使用 editable 时:uv sync --no-editable
  • 注意:如果项目没有定义构建系统(build system),则不会被安装。构建系统详见 项目配置文档

5.2 多余包(Extraneous packages)的处理

这是 uv syncuv run 的一个关键语义差异:

  • uv sync 默认执行"精确(exact)同步":会删除环境中不在锁文件里的包,保证环境与锁文件完全一致;
  • uv run 默认执行"非精确(inexact)同步":只确保必需的包都装上了,但不清理多余包。

对应标志:

$ uv sync --inexact        # 保留多余包
$ uv run --exact ...       # 让 uv run 也做精确同步

这一语义在源码中对应 Modifications 枚举Sufficient(pip install 语义,环境"够用"即可)与 Exact(pip sync 语义,多余安装一律移除),--inexact--exactCLI 定义 中互为 overrides_with 关系。

5.3 可选依赖(Extras)

uv 从 [project.optional-dependencies] 表读取可选依赖(即 extras)。默认不同步任何 extra,需要显式选择:

$ uv sync --extra foo        # 只装 foo 这个 extra
$ uv sync --all-extras      # 启用全部 extras

可选依赖的管理方式见 可选依赖

5.4 开发依赖(Dependency Groups)

uv 从 [dependency-groups] 表读取开发依赖(遵循 PEP 735)。几个要点:

  • dev 组是特殊组,默认参与同步(默认组机制见 默认组);
  • --no-dev:排除 dev 组;
  • --only-dev:只装 dev 组,不安装项目本身及其依赖
  • 其他组通过以下标志增删:--all-groups--no-default-groups--group <name>--only-group <name>--no-group <name>
  • --only-group 的语义与 --only-dev 相同(不含项目本身),但它同时会排除默认组。

排除永远优先于包含。例如:

$ uv sync --no-group foo --group foo

结果是 foo不会被安装。在源码层面,sync 函数 会先解析项目声明的默认组,再叠加命令行选择的组与 extra 集合(groups.with_defaults(...) / extras.with_defaults(...)),这个集合随后同时驱动虚拟环境创建与依赖安装。

开发依赖的完整管理方式见 开发依赖

六、升级已锁定的包版本

存在 uv.lock 时,uv syncuv lock优先沿用已锁定的版本。只有当项目依赖约束把旧版本排除在外时,版本号才会变化。升级操作:

$ uv lock --upgrade                    # 升级所有包
$ uv lock --upgrade-package <package>  # 只升级单个包到最新版
$ uv lock --upgrade-package <package>==<version>  # 升级到指定版本

所有升级都受项目依赖约束限制——例如你为某包声明了上界,升级就不会越过该上界。

Git 依赖逻辑类似:若 Git 依赖指向 main 分支,uv 优先使用锁文件中记录的 commit SHA,而不是 main 分支的最新 commit,除非使用 --upgrade / --upgrade-package

这些升级标志同样可以传给 uv syncuv run,一次完成"更新锁文件 + 更新环境"两步。

七、导出锁文件

如果要把 uv 集成到其他工具链,可以用 uv exportuv.lock 导出为多种格式:

$ uv export --format requirements.txt   # pip 兼容格式
$ uv export --format pylock.toml        # PEP 751
$ uv export --format cyclonedx1.5      # CycloneDX SBOM

各导出格式的完整文档见 export 指南

八、部分安装(Partial installs)

多阶段安装是 Docker 构建中做层缓存优化的常用手段,uv sync 提供三个标志支持这种场景:

标志 说明
--no-install-project 不安装当前项目本身
--no-install-workspace 不安装任何工作区成员(含根项目)
--no-install-package <NAME> 不安装指定的一个或多个包

注意两点:其一,目标包的所有依赖仍会被安装——--no-install-project 只是省略项目本身,不省略其依赖;其二,不当使用这些标志可能得到一个"缺了依赖"的破损环境,需要自行保证一致性。

九、同步时的恶意软件检查(Preview)

此功能处于 preview 阶段,稳定前可能变化。

同步过程中,uv 可以扫描锁文件、比对 OSV 数据库(其引用 OpenSSF 恶意软件包数据库的 MAL 通告)中的已知恶意软件。一旦锁定的依赖命中恶意软件通告,同步会直接终止,避免恶意包进入你的环境。

启用方式:

  • 在 uv 配置中设置 audit.malware-check = true,或设置环境变量 UV_MALWARE_CHECK=1
  • 使用第三方漏洞服务时,设置 audit.malware-check-url 或环境变量 UV_MALWARE_CHECK_URL

从源码结构看,该功能贯穿多个命令:sync 入口 接收 malware_settings: MalwareCheckSettings,并在执行锁操作前 保留一个独立的认证 client 供恶意软件检查查询使用(maybe_check_malware 调用点);uv adduv removeuv tree 等会触发锁定的命令同样接入了这一检查上下文,保证任何"改锁"入口都不会绕过恶意软件防线。

十、小结:一套标志覆盖完整生命周期

把全文收拢成一张决策表:

场景 命令/标志
信任 uv 自动维护 uv run(默认)
CI 中锁文件必须最新 --locked / uv lock --check
CI 中完全离线、跳过检查 --frozen
只做命令、不动环境 --no-sync
精确/宽松环境一致性 uv sync(默认 exact)/ --inexact / uv run --exact
升级 uv lock --upgrade / --upgrade-package
Docker 层缓存 --no-install-project 等部分安装标志
供应链安全 audit.malware-check = true(preview)

核心要点只有一条:锁文件是唯一的真源(single source of truth)。uv 的自动化只是在你背后默默执行"锁定 → 同步"这条流水线,而 --locked / --frozen / --no-sync / --inexact / --exact 这套标志位让你可以在本地开发、CI、容器构建等不同场景下,精确控制这条流水线的每一步行为。

登录后查看全文
热门项目推荐
相关项目推荐