第20篇|虚拟环境、依赖管理与打包
本篇定位:《Python 教程》专栏二「Python 进阶与工程」第 20 篇。承接第 01 篇的环境地基与第 09 篇的 import 机制,把「能跑」升级为「可复现、可交付」:虚拟环境隔离、
pyproject.toml声明、uv.lock钉死解析结果、wheel / 可编辑安装。为第 21 篇日志调试、第 22 篇 pytest、第 26~28 篇 HTTP / FastAPI / Agent 提供工程底座。读完你将能够:说清「环境 / 声明 / 锁 / 产物」四层分工;用
uv init/uv add/uv lock/uv sync/uv build走通项目生命周期;读懂并编写现代pyproject.toml(含 dependency-groups 与 optional-dependencies);解释为何 CI 要用--frozen,以及 AI 场景下训练 / 推理 / Agent 为何必须分环境。预计用时:阅读 35~45 分钟,动手 30~40 分钟。
第 01 篇解决了「装哪个 Python、别污染系统」;本篇回答更工程的问题——同一套代码,同事笔记本、CI、GPU 服务器上如何装出同一棵依赖树? 只靠「我这边 pip install 过」不够:传递依赖会漂移,CUDA 版 torch 会对不上,三周后的自己也会骂三周前的自己。
一句话定心:环境隔离防打架;声明写意图;锁文件钉快照;打包让别人不用抄目录。 第 01 篇的 uv init --no-package 是热身;本篇把 pyproject.toml 与 uv.lock 立成团队契约。
建议边读边开练习目录(uv init,本篇会引入小型第三方依赖做演示)。阅读顺序:四层心智 → pyproject 解剖 → uv 锁与同步 → 依赖分组 → 打包与可编辑安装 → AI Infra 多环境 → 踩坑。下一篇预告日志、调试与性能分析(21):环境钉死后,再谈「跑起来时怎么看见问题」。
和第 01 篇对照:那时重点是「解释器、PATH、site-packages」;本篇默认你会建 .venv,深入声明格式、锁文件、构建后端、交付物。和第 09 篇对照:import 找的是当前解释器的 site-packages;锁文件保证那棵树可重建。和第 18 篇对照:Pydantic 一旦进依赖,就必须进声明与锁,否则「合同」在别人机器上不存在。
动手时建议沿用「小推理客户端」意象:项目依赖 httpx + pydantic,开发组再加 pytest / ruff;用 uv sync 装齐,用 uv build 打出 wheel。做完后打开任意开源 AI 仓库的 pyproject.toml,你会认出同一套形状。
本篇不穷尽 conda 频道策略、不手写 setuptools 扩展模块、不部署 Docker 多阶段(镜像层会在后续服务篇触及)。主线是:心智模型 → 现代声明 → uv 锁纪律 → 基础打包 → 工程禁忌。和「随手 pip install 再 freeze」相比,现代工作流的价值是:意图与快照分离、可审查、可回滚。
写依赖时保持一个工程直觉:能写进 pyproject.toml 的,就不要只存在于聊天记录里。 「帮我装一下某某版本」如果不落文件,下次换机器就丢。社区里那些「README 写了半页 pip 命令却没有锁」的仓库,往往是复现地狱的入口。
也可以把本篇看成专栏二的「交付面」补课:第 15~19 篇把协议与积木立住;本篇把运行这些积木的环境工程化。后面日志、测试、并发、服务,都假设「依赖已钉死」。
读官方 Packaging User Guide、uv 文档与 PEP 517 / 518 / 621 / 735 时,按本篇目录对照即可。入门阶段把「会声明、会锁、会同步、会打 wheel」做扎实,比背完整构建后端表更重要。
动手时再记一条:锁文件进 Git 不是「把环境目录提交上去」——.venv 永远 gitignore;提交的是解析结果的文本快照。排障时 git diff uv.lock 往往比猜「谁升级了传递依赖」快一个数量级。
和「为每个实验新建一个 conda env 却从不记录包列表」相比,本篇推荐的默认策略是:一项目一目录一锁;升级开 PR;CI 禁止偷偷解析。 这个习惯在安全审计、事故回滚、开源贡献时都会回报你。
若你刚从第 19 篇「标准库电池」过来,本篇前两节会换一种爽感:同样是工程化,但挂钩点从「零依赖积木」换成「有依赖时如何钉死」。把「口头装包」改成 uv add,把「朋友机器装不上」改成 uv sync --frozen——两条路径走完,复现直觉就长出来了。
和「在 Wiki 里贴一段 pip 安装说明」相比,锁文件的价值是:机器可读、可 diff、可自动校验——CI、服务器、同事笔记本上同一份文本。排障时你面对的是版本号与哈希,而不是「我记得大概是 2.x」。
一、四层心智模型:环境、声明、锁、产物
1.1 一张图看清分工

图1 虚拟环境 · 依赖声明 · 打包:三层心智模型
| 层 | 核心问题 | 典型文件 / 命令 | 谁消费 |
|---|---|---|---|
| 虚拟环境 | 用哪套解释器与 site-packages? | .venv、uv venv | 本地进程 |
| 依赖声明 | 我想要哪些包、什么范围? | pyproject.toml | 人 + 解析器 |
| 锁文件 | 实际解析到哪些精确版本? | uv.lock | CI / 同步 |
| 打包产物 | 如何交付可安装物? | .whl / sdist | 安装方 |
# 最小可复现闭环(示意)
uv init demo-pack --python 3.12
cd demo-pack
uv add httpx pydantic
# 生成 / 更新 uv.lock,并同步 .venv
uv run python -c "import httpx, pydantic; print('ok')"
要点:
.venv是本地缓存,不是真相源——可删可重建;真相在 toml + lock。- 声明是区间,锁是快照——
pydantic>=2与pydantic==2.10.3职责不同。 - 打包是可选的第三步——应用仓库常只 sync;库仓库还要 build / publish。
1.2 和第 01 篇的边界
第 01 篇已讲:venv 很轻、activate 只是改 PATH、永远别 sudo pip 污染系统。本篇默认这些成立,聚焦:
- 为什么
requirements.txtalone 不够现代默认; pyproject.toml各段写什么;uv.lock如何生成、提交、在 CI 使用;- 可编辑安装与 wheel 分别何时用。
AI Infra 视角:vLLM 与训练栈常钉死不同的 torch 小版本。一台机器上「能 import torch」完全不够——必须问「哪一个环境的 torch、cu 几、锁文件是否与镜像一致」。多环境并存是常态,不是例外。
1.3 渐进采用
老项目只有 requirements.txt?可以:先 uv init 引入 pyproject.toml,把运行时依赖迁入 [project] dependencies,再 uv lock 生成锁;开发工具放进 dependency-groups。不必一天删光历史文件,但新约束只写一处真相。
二、pyproject.toml:现代项目的单一入口
2.1 三段结构

图2 pyproject.toml 解剖:声明、构建、工具配置
[project]
name = "demo-pack"
version = "0.1.0"
description = "A tiny packaging demo"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"httpx>=0.27",
"pydantic>=2.7",
]
[project.optional-dependencies]
cli = ["typer>=0.12"]
[dependency-groups]
dev = ["pytest>=8", "ruff>=0.6"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.uv]
package = true
说明:
[project](PEP 621):包名、版本、Python 下限、运行时依赖——对外承诺。[build-system](PEP 517/518):谁来打 wheel;纯 Python 常用hatchling。[tool.*]:uv / ruff / pytest 等工具私有配置,不自动变成「安装依赖」。[dependency-groups](PEP 735):开发 / 测试分组,默认不进发布元数据。optional-dependencies:可选特性,如pip install demo-pack[cli]。
2.2 应用仓库 vs 库仓库
| 类型 | uv init | 典型标志 | 交付物 |
|---|---|---|---|
| 应用 / 脚本 | --no-package | 无 build 压力 | 锁 + 源码 / 镜像 |
| 可安装库 | 默认 package | 有 [build-system] | wheel + 锁(可选) |
专栏练习多数用 --no-package 即可;一旦要把目录 import 成正式包名,就打开 packaging,补上 build-system。
uv init --no-package app-demo --python 3.12 # 应用
uv init lib-demo --python 3.12 # 可打包库(视 uv 版本默认)
2.3 版本约束怎么写
dependencies = [
"requests==2.32.3", # 精确:少用在库的对外依赖
"httpx>=0.27,<0.29", # 兼容区间:常见
"pydantic~=2.7", # 兼容 2.7.x 风格(注意语义)
]
库作者对外依赖宜宽(别过度钉死传递树);应用 / 服务靠 lock 钉死。两者不要混为一谈。
三、uv 锁文件:从「能装上」到「装一样」
3.1 工作流

pyproject.toml声明范围约束开始,经uv lock / add解析依赖树选具体版本,生成uv.lock(精确版本+哈希,跨平台标记),再通过uv sync装进venv与锁一致。日常开发中,uv add等操作改toml更新锁,uv run自动用venv,提交pyproject.toml和uv.lock。CI/服务器则用uv sync --frozen禁止解析源移,或uv sync --locked校验锁新鲜,镜像构建时同一把锁对应同一套包。底部强调声明是“区间”,锁是“精确快照”,锁文件应进版本控制,和业务代码同生命周期。图3 uv 依赖解析与锁文件工作流
uv add httpx # 改 pyproject + 更新 lock + sync
uv remove httpx # 对称操作
uv lock # 只解析,写 uv.lock
uv sync # 按锁安装到 .venv
uv sync --frozen # CI:锁已存在则禁止重新解析
uv tree # 查看依赖树
# uv tree 示意(版本号随时间变化)
demo-pack
├── httpx v0.27.x
│ ├── anyio …
│ ├── certifi …
│ └── httpcore …
└── pydantic v2.x
└── …
3.2 为什么锁必须进 Git
- 可 diff:升级时审查「谁被连带升级」。
- 可回滚:
git revert锁文件即回到已知好快照。 - 可审计:安全扫描对着精确版本,而不是「最新」。
不要提交 .venv/;要提交 uv.lock(以及 pyproject.toml)。若团队仍用 pip-tools,requirements.txt 由锁生成亦可,但应有单一生成源。
3.3 CI 纪律
# 推荐:与提交的锁严格一致
uv sync --frozen --all-groups
uv run pytest
--frozen:若锁与声明不一致则失败,而不是悄悄解析出「另一套」。本地开发用普通 uv sync;流水线用冻结模式——和「测试环境不可变基础设施」同一哲学。
3.4 与 pip freeze 的区别
pip freeze 导出当前环境已装列表,常混入间接依赖与本地路径,且无跨平台解析元数据。uv.lock 是解析器输出的跨平台锁,含哈希与标记。教学演示可以 freeze;团队复现请用 lock。
四、依赖分组、可选特性与覆盖源
4.1 运行时 vs 开发组
uv add pydantic # 写入 dependencies
uv add --dev pytest ruff # 写入 dependency-groups.dev(视 uv 版本)
uv sync --group dev # 显式同步某组
uv sync --all-groups # 本地开发一把梭
发布到 PyPI 的 wheel 不应把 pytest 打成硬依赖。分组让「装给用户的」和「开发者用的」分开。
4.2 可选特性
[project.optional-dependencies]
gpu = ["torch"] # 示意:真实 torch 常需额外 index
otel = ["opentelemetry-api"]
uv sync --extra gpu
# 或安装已发布包:
# pip install "mypkg[gpu]"
4.3 额外 index(PyTorch 常见)
# 示意:具体 URL / 版本随官方文档更新
[[tool.uv.index]]
name = "pytorch-cu124"
url = "https://download.pytorch.org/whl/cu124"
explicit = true
[tool.uv.sources]
torch = { index = "pytorch-cu124" }
AI Infra 视角:CUDA 版 wheel 不在默认 PyPI 主索引时,必须在配置里写清 index,并锁进
uv.lock。否则同事uv sync可能装到 CPU 版或解析失败。第 38 篇会再谈驱动 / toolkit / wheel 的层次。
五、打包:可编辑安装、sdist 与 wheel
5.1 三种产物心态

图4 从源码到可安装产物:sdist、wheel、可编辑安装
# 库项目内
uv sync # 常已含可编辑安装
uv build # 产出 dist/*.whl 与 sdist
ls dist/
dist/
demo_pack-0.1.0-py3-none-any.whl
demo_pack-0.1.0.tar.gz
纯 Python 的 py3-none-any wheel 跨平台;含原生扩展的 wheel 会带平台标签(manylinux、win_amd64 等)——那是另一门课。
5.2 包发现与 src 布局
现代默认倾向 src/demo_pack/,避免测试时误 import 到未安装的源码树。hatchling 按约定发现包;若布局特殊,在 [tool.hatch.build.targets.wheel] 中声明。
lib-demo/
pyproject.toml
src/
demo_pack/
__init__.py
client.py
tests/
test_client.py
5.3 入口脚本
[project.scripts]
demo-hello = "demo_pack.cli:main"
安装后即可在环境中运行 demo-hello。FastAPI 服务更常见的是镜像内 uv run uvicorn ...;入口点适合 CLI 工具。
5.4 什么时候还不必打包
课程练习、一次性分析脚本、只在私有 monorepo 内用的胶水代码:uv sync + 锁就够。引入 packaging 的信号是:别人需要 pip install 你的名字,或 IDE / 插件要以正式包名依赖你。
六、AI Infra:多环境、锁纪律与交付形态
6.1 为什么要拆环境

图5 AI Infra 视角:多环境并存与锁文件纪律
训练、vLLM 推理、FastAPI Agent 网关三者依赖冲突是常态。推荐:
- 物理隔离:不同目录 / 不同
.venv/ 不同 conda env; - 各自一把锁:不要幻想一个 lock 伺候所有栈;
- 镜像层固化:生产推理镜像在 build 时
uv sync --frozen,运行时只读。
6.2 与后续篇章的接口
- 第 21 篇:日志与调试假设「依赖已知」——版本不同,bug 形状不同。
- 第 22 篇:pytest 放在 dev 组,CI
uv sync --all-groups --frozen。 - 第 26~28 篇:HTTP / FastAPI / Agent 的
pyproject会越来越长,锁文件是救命绳。 - 第 38 篇:CUDA 与 device——环境选择直接影响能不能见到 GPU。
6.3 最小检查清单
- 仓库根有
pyproject.toml,运行时依赖写在[project]。 uv.lock已提交;.venv在.gitignore。- README 安装步骤是
uv sync(或等价),不是散装 pip 命令。 - CI 使用
--frozen(或等价锁定)。 - GPU / 私有源在 toml 中显式配置,不靠口头传说。
合上清单后,试着用自己的话说明:为什么「能 import」不等于「可复现」。讲得清,才算真的会了。
也可以把检查清单贴到团队 Wiki,但以仓库内文件为准——Wiki 过期时,锁文件仍在。
工程上还有一层隐藏收益:团队词汇统一。大家都说「声明 / 锁 / sync / frozen」,code review 依赖升级时不必解释「这段 requirements 是怎么来的」。共享词汇降低沟通税,这和类型注解降低形状沟通税是同一类投资。
把「升级依赖」当成一次可审查的变更:打开 PR,看 uv.lock diff 里是否出现意外的大版本跃迁,跑测试,再合并。比起在服务器上手搓 pip install -U,PR 流程给你回滚点与责任边界。
若你维护的是内部 SDK,再多想一步:语义化版本与 changelog。锁文件保证二进制一致;版本号与变更说明保证人类一致。两者缺一,排障时仍会「对不上号」。
对「只写脚本、不发包」的同学:你仍然需要锁。锁不是「库作者的税」,而是「任何会被第二台机器执行的代码」的税。CI、定时任务、GPU 队列 worker,都是第二台机器。
最后用一张对照表收束本节直觉:
| 做法 | 短期爽 | 三月后 |
|---|---|---|
| 口头装包 | 快 | 无法复现 |
| 只有 requirements 无哈希 | 能装 | 传递依赖漂移 |
| pyproject + uv.lock + CI frozen | 多一步 | 可 diff、可回滚、可审计 |
选第三行,是把未来的自己与同事算进成本函数里。
七、踩坑大全
通用
坑 1:改了依赖却忘了提交 uv.lock。 现象:CI 或同事 sync 出另一套版本。原因:声明与锁不同步。解决:uv lock / uv add 后一起 commit;CI 用 --frozen 抓漏。
坑 2:把 .venv 提交进 Git。 现象:仓库巨大、跨平台损坏。原因:环境是本地产物。解决:gitignore .venv;只提交 toml + lock。
坑 3:开发依赖写进 dependencies。 现象:用户安装你的库时被迫装 pytest。解决:用 dependency-groups 或 optional;发布前检查元数据。
坑 4:混用 pip install 与 uv add 且不写回 toml。 现象:环境能跑,克隆后缺包。原因:真相只在 site-packages。解决:一律通过 uv 改声明;或 uv add 补录。
坑 5:以为 requirements.txt 等于锁。 现象:无哈希、无标记、传递依赖漂移。解决:生成锁;若必须给老工具 requirements,由锁导出并注明自动生成。
坑 6:库对外依赖钉死精确小版本。 现象:与下游应用锁冲突。解决:库用兼容区间;应用用 lock 钉死。
坑 7:可编辑安装与错误的工作目录。 现象:改了代码 import 仍是旧行为。原因:装的是另一路径的包 / 未 editable。解决:uv sync 后 python -c "import pkg; print(pkg.file)" 核对路径。
macOS / Linux / Windows
坑 8:Windows 路径与脚本后缀。 现象:文档写 bin/activate,Windows 在 Scripts\。解决:优先 uv run,少手敲激活路径;脚本注意 PATHEXT。
坑 9:Linux 服务器 externally-managed-environment。 现象:系统 pip 拒绝安装。原因:PEP 668 保护系统 Python。解决:用 uv / venv / conda,不要强行破保护。
坑 10:公司代理 / 镜像只配了 pip 没配 uv。 现象:uv 拉包失败。解决:设 UV_DEFAULT_INDEX 或 toml index;与第 01 篇镜像提示一致。
与 AI / GPU 叠加
坑 11:默认 index 装到 CPU 版 torch。 现象:cuda.is_available() 为 False。解决:配置官方 CUDA wheel index 与 sources;锁文件审查包名标签。
坑 12:同一环境硬装 vLLM + 不兼容训练栈。 现象:解析失败或运行时奇异 bug。解决:分环境、分锁、分镜像;不要「一个 env 统治全部」。
八、小结
合上文章后,试着用自己的话说明:pyproject.toml 与 uv.lock 各回答什么问题,以及 CI 为何要用 --frozen。讲得清,才算真的会了。动手至少跑通:uv add → 查看 lock diff → uv sync --frozen →(库项目)uv build。
- 四层模型:环境隔离、声明意图、锁钉快照、打包交付。
pyproject.toml:[project]/[build-system]/[tool.*]/ groups / extras。- uv 工作流:
add/lock/sync/run/build;锁进 Git。 - 分组:运行时与开发分离;可选特性用 extras。
- 打包:editable 利于开发;wheel 利于交付;应用可不发布。
- AI Infra:训练 / 推理 / Agent 分环境分锁;GPU index 显式化。
把故事连起来:第 01 篇建环境 → 第 09 篇 import 找包 → 第 18~19 篇合同与积木 → 本篇把依赖钉死并可交付。专栏二工程刀具继续成套:下一站是日志、调试与性能分析(21)。
若只能带走一句话:声明写愿望,锁文件写事实;愿望可宽,事实必须可重建。
也可以用「三个不要」自检:不要提交 .venv;不要在 CI 里无锁解析;不要把 pytest 塞进用户依赖。
再留一个自测:打开一份开源 pyproject.toml,指出 dependencies、groups、build-system;说明若删除 lock 会失去什么——两问都顺,本篇就站稳了。
本篇技能会立刻复利:读框架仓库先看锁;开 PR 升级依赖先看 uv.lock diff;部署前确认镜像 build 用了 --frozen。后续日志、测试、服务、Agent,都假设这层已稳。
和专栏一串起来复习也不亏:环境(01)是地基;模块(09)解释查找;类型与标准库(18~19)是代码面——本篇是专栏二把「别人也能跑」立成一等约束的一课。
也可以把收获压成检查清单:会写 [project];会 uv add / sync --frozen;知道 lock 进 Git;会 uv build 或说明为何应用不需要;能举一个多环境 AI 场景。五项过关,即可进入日志与调试篇。
若把本篇压成给同事的三十秒讲解:先画「声明 → 锁 → sync」箭头;再强调「CI 冻结」;最后提「训练 / 推理分环境」。三张草图比贴一篇博客更管用。等对方能独立把散装 pip 项目迁到 uv + lock,并解释为何不提交 .venv,就可以一起 code review 依赖升级 PR 了。
动手清单再压缩一版:uv init 小项目;uv add httpx;提交 toml+lock;删 .venv 后 uv sync --frozen 恢复;若是库项目再 uv build。五步做完,比只「看完文章」扎实一个数量级。
九、课后练习
- (基础) 用
uv init --no-package建项目,uv add httpx,打开pyproject.toml与uv.lock,用自己的话各写一句「它回答什么问题」。 - (基础) 执行
uv add --dev pytest,确认 pytest 不在[project] dependencies而在开发组;uv run pytest --version。 - (基础) 删除
.venv后执行uv sync --frozen,确认环境恢复且未改写锁;故意改依赖版本区间后不 lock,再--frozen,记录报错信息。 - (理解) 把一个仅有
requirements.txt的旧目录迁到pyproject.toml:运行时进dependencies,测试工具进 group,生成uv.lock。 - (理解) 比较
uv tree与直接读 lock 片段,指出一个传递依赖是被谁引入的。 - (理解) 库项目:补上
[build-system],uv build,用uv pip install dist/*.whl装到新建的空环境,验证import。 - (进阶) 设计「API 服务」与「本地评测脚本」两套依赖组(或两个项目),说明哪些包共享、哪些必须隔离。
- (进阶 · AI Infra) 查阅当前 PyTorch 官方安装页,在 toml 中草稿一个 CUDA wheel 的 index / sources 配置(不必真下载大包),并写三段话:为何要与默认 PyPI 分开、锁文件如何帮助团队、若有人装到 CPU 版如何用
python -c自检。
下一篇预告
下一篇是《第21篇|日志、调试与性能分析》——在依赖钉死、环境可复现之后,解决「跑起来时如何看见问题」:logging 层级与配置、断点调试、cProfile / 简易计时、常见性能直觉。你会看到:没有结构化日志的服务,锁文件再完美也难排障;而过早微优化往往不如先确认「慢在 I/O 还是计算」。
专栏二后续路线:日志调试性能 → pytest → 并发与 asyncio → HTTP / FastAPI → 最小 Agent。带着本篇的锁文件纪律,读源码与部署文档时先找 pyproject / lock / 镜像 build 参数,再跟业务分支。
若想提前热身:给当前练习项目加两行 logging.basicConfig,用 uv run 跑起来观察级别;再对一段循环用 time.perf_counter 粗测。做完后第 21 篇谈 logger 层级与 profiler 时你会带着具体体感。
也可以把第 15~20 篇对照:生成器管流、装饰器管横切、上下文管资源、类型管形状、标准库给积木、本篇钉环境——工程六件套的轮廓已经清晰,下一篇把「可观测」也工程化。
专栏一用十四篇把「对象与协议」立住;专栏二从迭代器(15)走到依赖与打包(20),下一站是日志与调试。下一篇见。
若时间充裕,再多做一件事:打开你最近用过的一个开源 AI 项目,找到它的锁文件或 pin 策略,记三条「它如何防止依赖漂移」。这种阅读比空背命令更能把纪律刻进肌肉记忆。
进入专栏二至此,交付面与协议面开始交汇:会写代码,也要会让别人装到同一套代码。下一篇开始把运行时的「眼睛」与「听诊器」配齐。若某一节读着抽象,回到对应插图把箭头走一遍,再手打 uv add / uv sync --frozen——多数卡住的地方,其实是「声明」与「锁」被混成一件事。
写 pyproject 或审查锁 diff 时再记一条:错误往往来自真相有多份(README 一套、toml 一套、口头一套)。和「堆栈很长但根因很浅」相比,先合并真相源能省很多时间。
也可以把本篇看成专栏二的「复现面」补课:前面把协议与积木立住,本篇把环境契约收齐。后面日志篇教你如何观察;测试篇教你如何断言;FastAPI 与 Agent 篇则把钉死的依赖接到网络与工具调用上。
若阅读时觉得「应用还要不要打包」总搅在一起,回到图 4:应用靠 sync + 锁(或镜像);库才必须 wheel。日常编程两者都要懂,但职责不同——混谈是多数初学者卡住的根源。
收官前对照练习:同一份小项目,分别用「散装 pip + 口头版本」与「uv + lock + frozen」在第二台机器(或删 .venv)上复现。你会清楚感受到:锁文件不是纸面游戏,而是把「我这边能跑」抬到与团队契约同一套操作面的关键一步——也是专栏二工程化与服务 / Agent 对接的入场券之一。
写完练习后,不妨在自己的仓库里开一个小 PR:只引入 uv.lock 与同步的 README 安装步骤,不改业务语义。这种「小步替换」比空读文档更能把工作流刻进肌肉记忆,也更适合团队推广。
若你带新人,可以把本篇插图打印成一页速查:左列场景(加依赖 / CI / 发库 / GPU),右列命令。新人建项目时对着贴,两周后就会自然说出 --frozen 与「不提交 .venv」。工具文化是带出来的,不是靠一次分享会喊出来的。
最后把专栏二的位置再钉一次:协议篇(15~18)解决「怎么正确接线」;标准库篇(19)解决「手里有哪些趁手工具」;本篇解决「工具与依赖如何在每台机器上一致」。三者齐备,脚本才既写得快,又交得出去——下一篇再给它配上日志与调试的眼睛。
