这篇能做出什么
做完这篇,你会得到一个可以反复套用的项目骨架:
- 别人 clone 仓库后,只需一条
uv sync,虚拟环境、依赖版本就全部对齐,不用再问“你装的是哪个版本”。 - 提交代码前跑一条
uv run ty check,能在“把字符串当数字传进函数”这类问题上直接报错,而不是等上线后才发现。 - 在编辑器里写代码,保存后立刻看到红线,不用来回切终端。
- CI 里跑的是同一套命令,本地过不了就合不进去。
下面按“装工具 → 建项目 → 加依赖 → 接类型检查 → 接编辑器 → 接 CI”的顺序走一遍。
前置条件清单
- 一台能装软件的电脑,macOS、Linux、Windows 都行。
- 会用终端敲命令,知道
cd、ls。 - 装了 git;要做 CI 的话需要一个 GitHub 仓库。
- 一个编辑器,文里用 VS Code 举例,其他编辑器思路相同。
- 不需要预装 Python:uv 可以自己下载并管理解释器版本。
第一步:安装 uv 并验证
macOS / Linux:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
Windows PowerShell:
```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
也可以用 Homebrew、pipx 等包管理器安装,具体方式以 uv 官方文档当前说明为准。装完开一个新终端验证:
```bash
uv --version
uv python list
```
第二条会列出本机已有的和可下载的 Python 版本。如果你机器上一个 Python 都没有,这一步会提示可用的版本,后续 uv 会自动下载。
第二步:初始化项目
```bash
uv init --lib demo-ty
cd demo-ty
uv python pin 3.12
```
几点说明:
--lib生成 src 布局的库项目(src/demo_ty/),适合演示类型检查。如果你做的是脚本、服务,用uv init demo-ty即可,后面命令完全一样。uv python pin 3.12会把版本写进.python-version文件。这个文件要提交到 git,CI 和同事的机器都会读它,避免“我这儿是 3.13 你那儿是 3.9”的扯皮。版本号按项目需要填,以官方支持列表为准。- 初始化后目录里会有
pyproject.toml、.python-version、README.md、src/等文件。
第三步:添加依赖,看清发生了三件事
```bash
uv add httpx
uv add --dev pytest ty
```
第一条加运行时依赖,第二条加开发期工具(测试框架和类型检查器)。执行完,pyproject.toml 会长成这样(版本约束由 uv 自动补全,下面只示意结构):
```toml
[project]
name = "demo-ty"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"httpx",
]
[dependency-groups]
dev = [
"pytest",
"ty",
]
```
同时它还做了两件事:
1. 生成或更新 uv.lock,把每个包(包括间接依赖)的精确版本和哈希记下来。
2. 创建 .venv/ 虚拟环境并装好包。
uv.lock 必须提交到 git,.venv/ 不要提交。.gitignore 里加两行:
```gitignore
.venv/
__pycache__/
```
日常操作还有这些:
```bash
uv add "httpx>=0.27" # 带约束地加依赖
uv remove httpx # 删依赖,同时更新 lock
uv sync # 按 lock 把环境对齐(同事 clone 后跑这条)
uv sync --no-dev # 只装运行时依赖,用于生产镜像
uv tree # 看依赖树,排查“谁把这个包带进来的”
```
旧版本 uv 把开发依赖写在 [tool.uv] 下面,新版本用 PEP 735 的 [dependency-groups]。以 uv add --dev 实际生成的内容为准,不要手写覆盖。
第四步:用 uv run 跑任何东西
```bash
uv run python -c "import httpx; print(httpx.__version__)"
uv run pytest -q
```
uv run 会先检查环境是否和 lock 一致,不一致就先同步,然后才执行命令。所以不需要手动 source .venv/bin/activate;激活当然也可以,但在 CI 和脚本里,uv run 更省事。
如果环境已经同步过,想省掉检查开销,可以加 --no-sync:
```bash
uv run --no-sync pytest -q
```
第五步:接入 ty,先让它报一次错
在 src/demo_ty/pricing.py 写一段故意有问题的代码:
```python
def total(prices: list[float]) -> float:
return sum(prices)
if __name__ == "__main__":
print(total(["19.9", "29.9"]))
```
运行:
```bash
uv run ty check
```
它会指出:total 需要的是 list[float],你给的是字符串列表,参数类型不匹配。输出里会带上规则名,方便你按规则去查文档或做配置。
改成正确写法:
```python
if __name__ == "__main__":
print(total([19.9, 29.9]))
```
再跑一次,通过。这就是整条流水线里最核心的一条命令,后面 CI 里也是它。
想缩小检查范围可以指定路径:
```bash
uv run ty check src
```
ty 还在快速迭代,可配置项、规则名会变。写配置前,先跑一次看它报的规则名,再对照官方文档,不要照抄网上过期的片段。
第六步:把配置写进 pyproject.toml
在 pyproject.toml 末尾追加:
```toml
[tool.ty.environment]
python-version = "3.12"
python = ".venv"
```
environment.python 告诉 ty 去哪个虚拟环境找第三方库的类型信息。多数情况下 ty 自己能找到项目根下的 .venv,显式写死更稳,尤其是虚拟环境不在默认位置、或 CI 里路径不同时。
如果个别规则误报太多,可以按规则名调整等级:
```toml
[tool.ty.rules]
规则名以 ty check 输出里显示的为准
unresolved-import = "ignore"
```
原则是:只关掉确实误报的规则,不要图省事把整类检查关掉,否则类型检查就白接了。
第七步:编辑器集成
在 VS Code 扩展市场搜索 ty(Astral 官方发布),安装后打开项目根目录,扩展会启动 ty 的语言服务(底层命令是 ty server),实现保存即诊断、跳转定义、悬浮提示。
同时确认一下解释器选对了:命令面板执行 “Python: Select Interpreter”,选项目里的 .venv。如果编辑器里一切正常、终端里却报错,或者反过来,九成是解释器或工作目录没对上。
如果你原来装着 Pylance,两个类型检查器会重复报错。过渡期可以先把其中一个的诊断关掉,只留一个当权威来源。具体开关位置以扩展说明为准。
第八步:接进 CI
先确认 .python-version 和 uv.lock 都已提交。然后建 .github/workflows/ci.yml:
```yaml
name: ci
on:
push:
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Sync dependencies
run: uv sync --locked
- name: Type check
run: uv run --no-sync ty check
- name: Test
run: uv run --no-sync pytest -q
```
几个关键点:
astral-sh/setup-uv的版本 tag 以该仓库 README 当前说明为准,升级时同步改。uv sync --locked表示“lock 文件必须已经是最新的”,如果有人改了pyproject.toml却忘了uv lock,这一步会直接失败,而不是在 CI 里悄悄改版本。- setup-uv 会读取
.python-version并安装对应解释器,所以 CI 与本地的 Python 版本自然一致。 - 不需要额外写“激活虚拟环境”的步骤。
本地提交前,把这几条串成一条命令习惯:
```bash
uv run ty check && uv run pytest -q
```
想更省心可以加 pre-commit(同样用 uv add --dev pre-commit 装),把这条命令挂到提交钩子上。
常见问题与排错
1. 命令行里 import 报错,但代码明明写了依赖
大概率是绕过 uv 用了 pip install。uv sync 默认会把 .venv 严格对齐到 lock 文件,手工装进去的包可能被清掉。统一用 uv add,改完记得提交新的 uv.lock。
2. CI 上 uv sync --locked 失败
本地改了 pyproject.toml 没重新生成 lock。本地跑一次 uv lock,把 uv.lock 一起提交。
3. 编辑器不标红,命令行 ty check 正常
编辑器选错了解释器,或工作区根目录不是项目根目录。检查 .venv 是否存在、解释器是否指向它,然后重开窗口。
4. ty 报第三方库找不到类型
有些库不带类型标注,需要额外装类型存根包(常见命名是 types-xxx,以该库文档为准)。如果只是内部模块路径问题,检查 [tool.ty.environment] 里的 python 路径是否指向正确的虚拟环境。
5. ty 的配置项照抄后不生效
ty 处于快速迭代期,配置结构会调整。先看 ty check 的输出和 ty --help,再对照官方文档,不要用两年前的博客片段。
6. 多人改依赖,uv.lock 冲突
不要手工合并 lock 文件。让一方先合入,另一方拉最新代码后重新跑 uv lock,再提交。
下一步建议
- 加 ruff:同一家出品的格式化与 lint 工具,
uv add --dev ruff,然后uv run ruff check、uv run ruff format,和 ty 组成“格式 + 静态检查”双保险。 - 过渡期双检:ty 还在演进,关键项目可以先让 mypy 或 pyright 并行跑一段时间,比对两者的报错差异,再决定以谁为准。
- 多版本矩阵:如果项目要兼容多个 Python 版本,在 CI 里用矩阵跑不同解释器版本,依赖解析仍然统一走
uv sync --locked。 - 定期升级依赖:
uv lock --upgrade配合测试跑一遍,比攒半年再升要轻松得多。 - 把命令固化下来:写进
Makefile、justfile或 pre-commit 钩子,让“检查”变成默认动作,而不是靠自觉。
整条链路建好后,日常只剩三个动作:uv add 加依赖、uv run 跑命令、uv lock 提交锁定。类型检查从编辑器的红线一路延伸到 CI 的门禁,中间不需要任何手工同步。
