uv 项目环境实战:lock 与 sync 的锁定、同步机制全解
在 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: LockCheck 与 frozen: Option<FrozenSource> 两个参数,并在 锁模式判定逻辑 中按优先级映射为 LockMode::Frozen(frozen)、LockMode::Locked(locked/check)、LockMode::DryRun 或默认的 LockMode::Write(正常写入)。而 LockCheck 与 FrozenSource 枚举 还额外追踪了标志来源——来自 CLI、环境变量(如 UV_LOCKED=1、UV_FROZEN=1)还是工作区配置(locked / frozen 配置项),出错时能精确提示你是通过什么途径开启的检查。
三、锁文件新鲜度如何判定
uv 判断"锁文件是否过期"的依据是与项目元数据是否一致,核心规则有三条:
- 向
pyproject.toml新增依赖 → 锁文件视为过期; - 修改某依赖的版本约束、使已锁定的版本被排除 → 过期;
- 修改版本约束、但已锁定版本仍在约束范围内 → 锁文件仍然视为最新。
可以用 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 sync 与 uv run 的一个关键语义差异:
uv sync默认执行"精确(exact)同步":会删除环境中不在锁文件里的包,保证环境与锁文件完全一致;uv run默认执行"非精确(inexact)同步":只确保必需的包都装上了,但不清理多余包。
对应标志:
$ uv sync --inexact # 保留多余包
$ uv run --exact ... # 让 uv run 也做精确同步
这一语义在源码中对应 Modifications 枚举:Sufficient(pip install 语义,环境"够用"即可)与 Exact(pip sync 语义,多余安装一律移除),--inexact 与 --exact 在 CLI 定义 中互为 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 sync 和 uv 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 sync 或 uv run,一次完成"更新锁文件 + 更新环境"两步。
七、导出锁文件
如果要把 uv 集成到其他工具链,可以用 uv export 把 uv.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 add、uv remove、uv 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、容器构建等不同场景下,精确控制这条流水线的每一步行为。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00