﻿---
title: Python开发环境与工具链：VSCode、UV、Ruff
date: 2024-11-06
excerpt: "从解释器、虚拟环境、依赖管理、编辑器配置到 Ruff 检查格式化，系统搭建一套现代 Python 开发工作流，并解释每一步背后的取舍。"
tags: [Python, VSCode, Ruff, UV]
cover: https://assets.vluv.space/cover/Lang/Python/python1.webp
updated: 2026-05-08 22:34:07
---

<script data-swup-reload-script type="module" src="/js/components/tab.js"></script>

Python 的开发环境最容易让人困惑的地方，不是某个命令很难，而是工具太多：系统自带 Python、官网下载 Python、conda、venv、pip、pipx、poetry、pdm、pyenv、ruff、mypy、pytest、VS Code 插件。每个工具都能解决一部分问题，但边界不清楚时，项目很快就会变成“能跑，但是不知道为什么能跑”。

这篇文章按我更推荐的心智模型整理一套日常开发工具链：

- 用 `uv` 管 Python 版本、虚拟环境和依赖。
- 用 `pyproject.toml` 放项目元数据和工具配置。
- 用 VS Code + Pylance 负责编辑器补全、跳转和类型提示。
- 用 Ruff 负责格式化、导入排序和静态检查。

目标不是追求工具新，而是让环境可复现、命令少、项目根目录干净，并且换一台机器后也能尽量少猜。

<script data-swup-reload-script type="module" src="/js/components/text-image-section.js"></script>
<text-image-section image="https://assets.vluv.space/Python/pydevlog1/benchmark.webp" alt="uv dependency resolution benchmark" width="420px">

`uv` 吸引人的地方首先是快，但更重要的是它把过去分散在 `pip`、`pip-tools`、`virtualenv`、`pipx`、`pyenv` 和部分项目管理工具里的能力收束到一个命令下。速度只是入口，统一的工作流才是长期收益。

如果只是偶尔写脚本，`python -m venv` 加 `pip` 仍然能工作；如果你维护的是需要多人协作、持续测试、锁定依赖和统一格式的项目，`uv + Ruff + pyproject.toml` 会更省心。

</text-image-section>

## 一套环境要解决什么问题

一个 Python 项目至少有四层状态：

| 层级 | 典型问题 | 推荐承载方式 |
| --- | --- | --- |
| Python 解释器 | 项目到底用 3.10、3.11 还是 3.12？ | `.python-version`、`requires-python`、`uv python` |
| 虚拟环境 | 依赖安装到哪里，是否污染系统环境？ | `.venv` |
| 依赖声明 | 项目直接依赖哪些库？开发依赖有哪些？ | `pyproject.toml` |
| 依赖锁定 | 每个库最终解析到哪个精确版本？ | `uv.lock` 或导出的 `requirements.txt` |

过去常见的问题是这四层混在一起。例如 `pip install requests` 会把包安装进当前环境，但不会自然回答“这个包应该写入哪里”“别人如何复现”“Python 版本不一致怎么办”。`uv` 的项目模式把这些问题串起来：

```shell
uv init demo
cd demo
uv add requests
uv run python main.py
```

运行这些命令后，项目里会逐步出现：

<script data-swup-reload-script type="module" src="/js/components/tree.js"></script>
<x-tree root="demo">

- .python-version
- .venv/
- README.md
- main.py
- pyproject.toml
- uv.lock

</x-tree>

这里的重点不是文件多了，而是职责清楚了：

- `.python-version` 说明这个目录希望使用哪个 Python。
- `.venv` 是项目本地虚拟环境，不应该提交到 Git。
- `pyproject.toml` 记录项目元数据和直接依赖。
- `uv.lock` 记录完整解析结果，适合提交到 Git 以保证协作机器一致。

## 安装 Uv

官方文档入口是 [uv docs](https://docs.astral.sh/uv/)。独立安装器不依赖本机已有 Python，更适合作为第一选择。

<x-tabs>

<x-tab title="macOS / Linux" active>

```shell
curl -LsSf https://astral.sh/uv/install.sh | sh
```

安装完成后重新打开终端，或者按安装器提示刷新 shell 配置。然后检查版本：

```shell
uv --version
```

</x-tab>

<x-tab title="Windows PowerShell">

```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

安装完成后重新打开 PowerShell，然后检查版本：

```powershell
uv --version
```

</x-tab>

<x-tab title="pip">

如果环境里已经有可用的 Python，也可以用 pip 安装：

```shell
pip install uv
```

这种方式适合临时机器或 CI 镜像，但长期个人机器更推荐独立安装器，因为它不依赖某个特定 Python 环境。

</x-tab>

</x-tabs>

## 管理 Python 版本

Python 项目最常见的隐性 bug，是作者本地用 3.12，CI 或同事机器用 3.10。语法、标准库、类型注解和依赖解析都可能因此不同。

先查看可用版本：

```shell
uv python list
```

安装指定版本：

```shell
uv python install 3.12
```

在当前项目固定 Python 版本：

```shell
uv python pin 3.12
```

这个命令会写入 `.python-version`。之后在项目目录里执行 `uv run`、`uv sync` 等命令时，`uv` 会优先按这个版本找解释器；如果找不到，在允许下载的情况下会自动下载对应版本。

还可以在 `pyproject.toml` 里声明项目支持的 Python 范围：

```toml
[project]
requires-python = ">=3.12"
```

`.python-version` 和 `requires-python` 不是一回事：

| 文件 / 字段 | 作用 |
| --- | --- |
| `.python-version` | 本地工作目录偏好的解释器版本，偏向开发体验 |
| `project.requires-python` | 项目声明支持的 Python 版本范围，偏向包元数据和依赖解析 |

个人项目里可以两者都写。例如要求项目支持 `>=3.12`，本地当前固定在 `3.12` 或 `3.13`。

## 创建项目

新项目推荐直接用 `uv init`：

```shell
uv init my-app
cd my-app
```

如果当前目录已经是项目目录：

```shell
uv init
```

运行示例程序：

```shell
uv run python main.py
```

第一次执行项目命令时，`uv` 会创建 `.venv`、解析依赖，并生成 `uv.lock`。和手动激活虚拟环境相比，`uv run` 的好处是命令更明确：你是在“项目环境”里运行这个命令，而不是依赖当前 shell 是否已经激活了正确环境。

例如：

```shell
uv run python --version
uv run python -c "import sys; print(sys.executable)"
```

第二条命令应该输出项目 `.venv` 里的 Python 路径。这是检查环境是否正确的最直接方法。

## 虚拟环境：要不要手动激活

`uv` 支持两种使用方式。

第一种是不激活环境，所有命令都加 `uv run`：

```shell
uv run python main.py
uv run pytest
uv run ruff check .
```

这适合脚本、CI 和文档，因为命令自带上下文，不依赖 shell 状态。

第二种是激活 `.venv`，之后直接运行命令：

<x-tabs>

<x-tab title="Bash / Zsh" active>

```shell
source .venv/bin/activate
python main.py
deactivate
```

</x-tab>

<x-tab title="PowerShell">

```powershell
.\.venv\Scripts\Activate.ps1
python main.py
deactivate
```

</x-tab>

<x-tab title="Nushell">

```nu
overlay use .venv/bin/activate.nu
python main.py
deactivate
```

Windows 下 Nushell 的路径通常是：

```nu
overlay use .venv/Scripts/activate.nu
```

</x-tab>

</x-tabs>

我的习惯是：日常终端里可以激活，文档和自动化脚本里尽量用 `uv run`。这样不会把“我当前 shell 的状态”变成项目构建的一部分。

## 添加、更新和删除依赖

添加运行时依赖：

```shell
uv add requests
```

添加开发依赖：

```shell
uv add --dev pytest ruff
```

指定版本：

```shell
uv add "ruff==<version>"
uv add "fastapi>=0.115"
```

删除依赖：

```shell
uv remove requests
uv remove --dev ruff
```

查看依赖树：

```shell
uv tree
```

这里需要区分 `uv add` 和 `uv pip install`：

| 命令 | 适合场景 | 是否写入项目依赖 |
| --- | --- | --- |
| `uv add <pkg>` | 项目依赖管理 | 是，写入 `pyproject.toml` 并更新锁文件 |
| `uv pip install <pkg>` | 兼容旧的 pip 工作流或临时环境操作 | 通常不改项目元数据 |
| `uv run --with <pkg> <cmd>` | 临时带一个包运行命令 | 不持久写入项目 |

例如临时运行一个工具：

```shell
uv run --with rich python -c "from rich import print; print('[green]hello[/green]')"
```

如果这个包是项目长期依赖，就不要用临时命令，应该 `uv add rich`。

## 同步和锁定依赖

协作项目里，推荐提交 `uv.lock`。别人拉取项目后运行：

```shell
uv sync
```

`uv sync` 会让 `.venv` 和项目声明、锁文件保持一致。根据官方 CLI 说明，项目环境不存在时会创建；默认同步会移除未声明的额外包，所以它比“只安装缺的包”更适合复现环境。

常用命令：

```shell
# 只更新锁文件
uv lock

# 根据锁文件同步环境
uv sync

# 同步后运行命令
uv run pytest
```

如果你需要给不使用 uv 的环境提供 `requirements.txt`，可以导出：

```shell
uv export --format requirements-txt --output-file requirements.txt
```

老的 pip-tools 风格也可以用：

```shell
uv pip compile pyproject.toml -o requirements.txt
uv pip sync requirements.txt
```

选择哪一种，取决于项目边界：

- 项目内部开发：优先 `uv.lock` + `uv sync`。
- 部署平台只认 `requirements.txt`：用 `uv export` 或 `uv pip compile` 生成。
- 旧项目迁移：先保留 `requirements.txt`，用 `uv pip` 接管安装速度，再慢慢迁移到 `pyproject.toml`。

## VS Code 插件

推荐安装这些插件：

- `Python`
- `Pylance`
- `Ruff`
- `Python Debugger`
- `Python postfix completion`

各自职责不同：

| 插件 | 主要职责 |
| --- | --- |
| Python | 选择解释器、运行测试、调试入口 |
| Pylance | 语言服务、跳转、补全、类型检查 |
| Ruff | 格式化、导入排序、Lint 诊断和自动修复 |
| Python Debugger | 调试 Python 程序 |
| Python postfix completion | 后缀补全，提升编辑效率 |

安装插件后，先在 VS Code 命令面板里选择解释器：

```text
Python: Select Interpreter
```

选择项目里的 `.venv`。如果没有看到 `.venv`，先在项目根目录运行：

```shell
uv sync
```

## VS Code 保存时格式化和修复

在项目级 `.vscode/settings.json` 中写配置，比写全局配置更可复现。一个基础配置如下：

```json
{
  "editor.formatOnPaste": true,
  "editor.formatOnSave": true,
  "editor.formatOnType": true,
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.codeActionsOnSave": {
      "source.fixAll.ruff": "always",
      "source.organizeImports.ruff": "always"
    }
  }
}
```

这段配置做三件事：

- 保存时调用 Ruff formatter。
- 保存时用 Ruff 执行可自动修复的 lint。
- 保存时整理 import。

如果项目已经使用 Black，也可以用微软维护的 Black Formatter 插件。但对新项目来说，用 Ruff 同时负责格式化和 lint，配置更少，速度也更一致。

## Pylance 配置

Pylance 负责“编辑时理解代码”。它和 Ruff 的边界要分清楚：

- Ruff 更像快速代码质量工具：格式、导入、常见错误、风格规则。
- Pylance 更像语言智能：类型推断、跳转、补全、引用查找。

可以从下面这份配置开始：

```json
{
  "python.languageServer": "Pylance",
  "python.analysis.cacheLSPData": true,
  "python.analysis.autoFormatStrings": true,
  "python.analysis.autoImportCompletions": true,
  "python.analysis.completeFunctionParens": true,
  "python.analysis.typeCheckingMode": "basic",
  "editor.inlayHints.enabled": "offUnlessPressed",
  "python.analysis.inlayHints.callArgumentNames": "all",
  "python.analysis.inlayHints.functionReturnTypes": true,
  "python.analysis.inlayHints.pytestParameters": true,
  "python.analysis.inlayHints.variableTypes": true,
  "python.terminal.activateEnvInCurrentTerminal": true,
  "python.terminal.shellIntegration.enabled": true
}
```

`typeCheckingMode` 有几个常用值：

| 值 | 适合场景 |
| --- | --- |
| `off` | 只想要补全和跳转，不想看到类型报错 |
| `basic` | 日常项目推荐起点 |
| `strict` | 类型约束较强的项目，或者核心库、长期维护项目 |

我不建议一上来就给所有项目开 `strict`。严格类型检查很有价值，但它也会暴露很多历史代码问题。如果项目已有大量动态代码，可以先从 `basic` 开始，把新增模块写干净，再逐步提高标准。

## Ruff：格式化和静态检查

[Ruff](https://docs.astral.sh/ruff/) 是 Astral 写的 Python linter 和 formatter。它常见的替代对象包括 Flake8、isort、部分 pyupgrade 规则和 Black 的一部分格式化工作。

安装到项目开发依赖：

```shell
uv add --dev ruff
```

手动运行检查：

```shell
uv run ruff check .
```

自动修复可修复问题：

```shell
uv run ruff check . --fix
```

格式化代码：

```shell
uv run ruff format .
```

检查格式但不改文件，适合 CI：

```shell
uv run ruff format . --check
```

Ruff 的配置可以放在 `pyproject.toml`、`ruff.toml` 或 `.ruff.toml`。项目已有 `pyproject.toml` 时，直接集中到里面通常更清爽。

```toml
[tool.ruff]
line-length = 100
target-version = "py312"
respect-gitignore = true
indent-width = 4

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
docstring-code-format = true

[tool.ruff.lint]
select = [
  "E",    # pycodestyle error
  "F",    # pyflakes
  "I",    # isort
  "UP",   # pyupgrade
  "B",    # flake8-bugbear
  "SIM"   # flake8-simplify
]
ignore = []
```

几点经验：

- `line-length = 100` 对现代屏幕比较舒服，团队也常用 88 或 120，关键是统一。
- `target-version` 应该和项目支持的 Python 版本一致。Ruff 会据此决定能否使用较新的语法改写。
- `select` 不要一次开太激进。先从 `E/F/I/UP/B` 开始，稳定后再扩。
- 如果你已经在 `[project]` 里写了 `requires-python`，Ruff 官方也能从中推断目标版本；显式写 `target-version` 时，以 Ruff 配置为准。

## Git Hook：推送前检查

项目里最好把检查命令固化下来。最简单的方式是写一个 `pre-push` hook：

```shell
#!/bin/sh

uv run ruff format . --check
uv run ruff check .
uv run pytest
```

保存为：

```text
.git/hooks/pre-push
```

然后给执行权限：

```shell
chmod +x .git/hooks/pre-push
```

这段脚本故意不自动修复，因为 push 阶段应该负责拦截问题，而不是悄悄修改工作区。自动修复更适合在编辑器保存时或手动执行：

```shell
uv run ruff check . --fix
uv run ruff format .
```

如果团队协作，建议用 `pre-commit` 框架或 CI，而不是只依赖本地 `.git/hooks`，因为 Git hook 默认不会随仓库克隆自动启用。

## pyproject.toml 示例

`pyproject.toml` 是现代 Python 项目的中心配置文件。它来自 PEP 518 之后的生态演进，用来减少 `setup.py`、`setup.cfg`、`requirements.txt`、各种工具配置文件分散的问题。

一个常见项目可以这样写：

```toml
[project]
name = "vnet"
version = "0.1.0"
description = "Virtual network playground"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
  "pydantic>=2.10",
]

[dependency-groups]
dev = [
  "pytest>=8.3",
  "pytest-cov>=6.0",
  "ruff",
]
doc = [
  "mkdocs-material>=9.5",
  "mkdocs-mermaid2-plugin>=1.1",
]

[tool.ruff]
line-length = 100
target-version = "py312"
respect-gitignore = true
indent-width = 4

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
docstring-code-format = true

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]
ignore = ["E402"]

[tool.pytest.ini_options]
addopts = "-ra"
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
log_cli = true
log_cli_format = "%(asctime)s [%(levelname)s] %(message)s"
log_cli_date_format = "%m.%d %H:%M:%S"
```

这里有几个容易踩坑的点：

- `dependencies` 放运行时依赖，也就是你的包真正运行需要的库。
- `dev` 这类开发依赖不要混进运行时依赖，否则部署环境会变重。
- `pythonpath` 不建议随手写成个人机器绝对路径，例如 `/home/name/workspace/project`。这会让配置只在你的电脑上生效。更推荐采用标准包结构，或者在测试命令中明确设置。
- `indent-style = "tab"` 虽然 Ruff 支持，但 Python 社区更常见的是 space。除非团队明确要求 tab，否则不建议改。

## 一个更完整的项目骨架

如果项目不只是单文件脚本，推荐用 `src` layout：

<x-tree root="vnet">

- pyproject.toml
- uv.lock
- README.md
- src/
  - vnet/
    - __init__.py
    - main.py
- tests/
  - test_main.py

</x-tree>

这样可以避免“当前目录刚好在 `sys.path` 里，所以测试能过，安装后反而不能 import”的问题。

示例：

```python
# src/vnet/main.py
def add(left: int, right: int) -> int:
    return left + right
```

```python
# tests/test_main.py
from vnet.main import add


def test_add() -> None:
    assert add(1, 2) == 3
```

运行：

```shell
uv run pytest
uv run ruff check .
uv run ruff format . --check
```

如果三条命令都能过，这个项目的基本开发闭环就成立了。

## `__pycache__` 和临时文件

Python 会把字节码缓存写到 `__pycache__`，pytest 会写 `.pytest_cache`，Ruff 会写 `.ruff_cache`。这些目录一般不需要提交。

`.gitignore` 至少写：

```txt
.venv/
__pycache__/
*.py[cod]
.pytest_cache/
.ruff_cache/
.mypy_cache/
.coverage
htmlcov/
dist/
build/
```

需要清理时：

```shell
find . -type d -name "__pycache__" -prune -exec rm -r {} +
find . -type d -name ".pytest_cache" -prune -exec rm -r {} +
find . -type d -name ".ruff_cache" -prune -exec rm -r {} +
find . -type f -name "*.pyc" -delete
```

Windows PowerShell：

```powershell
Get-ChildItem -Recurse -Directory -Filter "__pycache__" | Remove-Item -Recurse -Force
Get-ChildItem -Recurse -Directory -Filter ".pytest_cache" | Remove-Item -Recurse -Force
Get-ChildItem -Recurse -Directory -Filter ".ruff_cache" | Remove-Item -Recurse -Force
Get-ChildItem -Recurse -File -Filter "*.pyc" | Remove-Item -Force
```

清理缓存不是日常必须操作。通常只有在路径混乱、测试行为奇怪、或者打包前想保持目录干净时才需要。

## dataclass：小而常用的数据建模工具

在 Python 中，定义一个只负责承载数据的类，经常会写重复代码：`__init__`、`__repr__`、`__eq__`。`dataclass` 用装饰器帮你生成这些样板代码。

```python
from dataclasses import dataclass


@dataclass
class User:
    name: str
    age: int


user = User("alice", 18)
print(user)
```

输出类似：

```text
User(name='alice', age=18)
```

如果希望对象创建后不可变：

```python
from dataclasses import dataclass


@dataclass(frozen=True)
class Point:
    x: int
    y: int


p = Point(10, 20)
# p.x = 30  # 会报错，因为 frozen=True 禁止修改字段
```

需要隐藏敏感字段：

```python
from dataclasses import dataclass, field


@dataclass
class Account:
    username: str
    password: str = field(repr=False)


account = Account(username="alice", password="secret")
print(account)
```

输出不会包含密码：

```text
Account(username='alice')
```

需要转成字典：

```python
from dataclasses import asdict, dataclass


@dataclass
class Point:
    x: int
    y: int


payload = asdict(Point(1, 2))
```

`dataclass` 适合轻量数据对象。如果你需要复杂校验、JSON schema、API 入参出参模型，`Pydantic` 往往更合适；如果只是内部函数之间传递结构化数据，`dataclass` 已经足够。

## 日常命令清单

下面这组命令基本覆盖个人项目从初始化到检查的流程：

```shell
# 创建项目
uv init my-app
cd my-app

# 固定 Python 版本
uv python pin 3.12
uv sync

# 添加依赖
uv add requests
uv add --dev pytest ruff

# 运行
uv run python main.py
uv run pytest

# 检查和格式化
uv run ruff check .
uv run ruff check . --fix
uv run ruff format .

# 查看依赖
uv tree

# 同步环境
uv sync
```

## 该怎么取舍

如果只是一次性脚本，最小可用方案是：

```shell
uv run --with requests script.py
```

如果是长期维护项目，推荐：

```shell
uv init
uv add --dev pytest ruff
```

然后把 `pyproject.toml`、`uv.lock`、`.python-version` 和 `.vscode/settings.json` 维护好。

如果是已有老项目，不要急着一次性替换所有工具。可以按这个顺序迁移：

1. 先用 `uv pip sync requirements.txt` 替代慢速安装。
2. 再把 Ruff 接进编辑器和 CI。
3. 最后把依赖声明迁到 `pyproject.toml`，用 `uv.lock` 固化解析结果。

工具链的理想状态不是“每个工具都最新”，而是每个问题只有一个明确答案：Python 版本在哪里定，依赖在哪里声明，环境怎么复现，格式和检查由谁负责。做到这一步，Python 项目就会少很多玄学。

## References

- [uv official documentation](https://docs.astral.sh/uv/)
- [uv CLI reference](https://docs.astral.sh/uv/reference/cli/)
- [uv project layout](https://docs.astral.sh/uv/concepts/projects/layout/)
- [Ruff documentation](https://docs.astral.sh/ruff/)
- [Ruff configuration](https://docs.astral.sh/ruff/configuration/)
- [Ruff editor settings](https://docs.astral.sh/ruff/editors/settings/)
- [Writing your pyproject.toml](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/)
- [dataclasses — Data Classes](https://docs.python.org/3/library/dataclasses.html)
