2972 字
15 分钟
Python 项目工程化实战(一):pyproject.toml + uv + Ruff 现代工具链从零搭建 | Python 进阶
你的 Python 项目是否还在用 setup.py + requirements.txt + .flake8 + setup.cfg + tox.ini……一堆散落的配置文件维护着?2026 年,Python 工具链已经全面现代化:
pyproject.toml:PEP 621 标准,一个文件统管所有配置uv:Rust 编写的超快包管理器,替代 pip + venv + poetryRuff:Rust 编写的 linter+formatter,替代 flake8 + isort + Black
本文是工程化系列第一篇,带你从零搭建一个完整的现代化 Python 项目结构。
一、为什么要告别老式工具链?
老式工具链的问题
传统 Python 项目配置文件散落情况:
my_project/├── setup.py # 构建配置(需要执行 Python 代码才能读取!)├── setup.cfg # 部分配置迁移到这里├── requirements.txt # 生产依赖├── requirements-dev.txt # 开发依赖(手动维护,容易遗漏)├── .flake8 # flake8 配置├── .isort.cfg # isort 配置├── mypy.ini # mypy 配置├── pytest.ini # pytest 配置├── tox.ini # tox 配置└── pyproject.toml # 越来越多工具开始迁移到这里 # 但和其他文件并存,混乱问题清单:
- 配置分散在 5-8 个文件,维护成本高
setup.py是可执行代码,无法静态解析,影响构建工具性能requirements.txt无法区分生产依赖/开发依赖/可选依赖- 缺少 lock 文件机制,
pip install -r requirements.txt结果不可复现 - 多个 linting 工具各自跑,速度慢,配置互相干扰
现代工具链的目标
my_project/├── pyproject.toml # ✅ 唯一配置文件,包含所有工具配置├── uv.lock # ✅ 自动生成的精确依赖 lock 文件├── src/│ └── my_project/ # ✅ src layout,避免包名冲突│ ├── __init__.py│ └── main.py└── tests/ └── test_main.py二、uv:下一代 Python 包管理器
2.1 安装 uv
# macOS / Linux(推荐,单命令安装)curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows(PowerShell)powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# 验证安装uv --version# uv 0.4.xuv 是单一二进制文件,安装后无需 Python 环境,自己管理 Python 版本。
2.2 uv 核心命令速查
# ── 项目初始化 ──────────────────────────────────────────────uv init my-project # 创建新项目(自动生成 pyproject.toml)uv init --lib my-package # 创建库项目(src layout)
# ── Python 版本管理 ──────────────────────────────────────────uv python install 3.12 # 安装指定 Python 版本uv python list # 查看已安装的 Python 版本uv python pin 3.12 # 固定当前项目使用的 Python 版本(写入 .python-version)
# ── 虚拟环境 ─────────────────────────────────────────────────uv venv # 创建虚拟环境(自动使用项目指定的 Python 版本)uv venv --python 3.11 # 指定 Python 版本创建source .venv/bin/activate # Linux/macOS 激活.venv\Scripts\activate # Windows 激活
# ── 依赖管理 ─────────────────────────────────────────────────uv add requests # 添加生产依赖(自动更新 pyproject.toml + uv.lock)uv add --dev pytest ruff # 添加开发依赖uv add --optional ml numpy # 添加可选依赖组uv remove requests # 移除依赖
uv sync # 按 uv.lock 同步环境(CI/CD 中使用)uv sync --frozen # 严格模式:不允许更新 lock 文件(适合生产部署)uv lock # 更新 lock 文件但不安装uv tree # 查看依赖树
# ── 运行 ─────────────────────────────────────────────────────uv run python main.py # 在项目虚拟环境中运行命令uv run pytest # 运行测试uv run -- my-cli-command # 运行项目安装的 CLI
# ── pip 兼容模式(迁移旧项目时使用)──────────────────────────uv pip install requests # 与 pip install 语法一致uv pip install -r requirements.txtuv pip freeze # 输出当前环境的包列表2.3 速度对比
# 测试:安装 Django + 所有依赖
# pip(冷缓存)time pip install django# 约 12-15 秒
# uv(冷缓存)time uv pip install django# 约 1-2 秒
# uv(热缓存,已下载过)time uv pip install django# 约 0.1-0.3 秒(从本地缓存安装,几乎即时)uv 使用全局缓存(~/.cache/uv/),同一个包在多个项目之间共享缓存,避免重复下载。
三、pyproject.toml:统一配置文件
3.1 完整模板(含注释)
# pyproject.toml —— Python 项目统一配置文件
# ── 构建系统 ──────────────────────────────────────────────────# 声明使用哪个构建后端(用于 pip install / python -m build)[build-system]requires = ["hatchling"] # hatchling 是现代轻量构建后端(uv 默认使用)build-backend = "hatchling.build"
# 其他常见选择:# requires = ["setuptools>=61", "wheel"] # 传统方案# requires = ["poetry-core"] # poetry 方案# requires = ["flit_core>=3.2"] # flit 方案(简洁)
# ── 项目元信息(PEP 621 标准)────────────────────────────────[project]name = "my-awesome-project"version = "0.1.0"description = "A short description of the project"readme = "README.md"license = { file = "LICENSE" }authors = [ { name = "Your Name", email = "you@example.com" },]keywords = ["python", "example"]classifiers = [ "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12",]
# 支持的 Python 版本范围requires-python = ">=3.11"
# 生产依赖(等价于老式 requirements.txt)dependencies = [ "requests>=2.31", "pydantic>=2.0", "httpx>=0.27",]
# ── 可选依赖组(pip install my-project[ml])──────────────────[project.optional-dependencies]ml = [ "numpy>=1.26", "scikit-learn>=1.4",]dev = [ "pytest>=8.0", "pytest-cov>=5.0", "ruff>=0.5", "mypy>=1.10",]
# ── 命令行入口点 ──────────────────────────────────────────────[project.scripts]# pip install 后,my-cli 命令可用my-cli = "my_project.cli:main"
# ── 项目 URL ─────────────────────────────────────────────────[project.urls]Homepage = "https://github.com/yourname/my-project"Documentation = "https://my-project.readthedocs.io"Repository = "https://github.com/yourname/my-project""Bug Tracker" = "https://github.com/yourname/my-project/issues"
# ── Hatchling 构建配置 ────────────────────────────────────────[tool.hatch.build.targets.wheel]packages = ["src/my_project"] # src layout:指定包在 src/ 下
# ── uv 配置 ──────────────────────────────────────────────────[tool.uv]dev-dependencies = [ # 开发依赖(等价于 [project.optional-dependencies].dev) "pytest>=8.0", "pytest-cov>=5.0", "ruff>=0.5", "mypy>=1.10",]
# ── Ruff:linter + formatter 配置 ────────────────────────────[tool.ruff]target-version = "py311" # 目标 Python 版本line-length = 120 # 每行最大字符数(Black 默认 88,可按需调整)src = ["src", "tests"] # 告知 Ruff 源码目录(影响 import 排序)
[tool.ruff.lint]select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # pyflakes(未使用变量/导入等) "I", # isort(import 排序) "N", # pep8-naming(命名规范) "UP", # pyupgrade(自动升级旧语法) "B", # flake8-bugbear(常见 bug 模式) "SIM", # flake8-simplify(代码简化建议) "C4", # flake8-comprehensions(列表推导式优化) "RUF", # Ruff 自有规则]ignore = [ "E501", # 行长度(由 formatter 控制,不重复报告) "B008", # 不允许函数调用作为默认参数(FastAPI 中常用,需忽略)]
[tool.ruff.lint.isort]known-first-party = ["my_project"] # 声明第一方包,影响 import 排序分组
[tool.ruff.format]quote-style = "double" # 字符串使用双引号(与 Black 默认一致)indent-style = "space"skip-magic-trailing-comma = false
# ── mypy 静态类型检查配置 ─────────────────────────────────────[tool.mypy]python_version = "3.11"warn_return_any = truewarn_unused_configs = truedisallow_untyped_defs = true # 所有函数必须有类型注解ignore_missing_imports = true # 忽略没有 stubs 的第三方库
# ── pytest 测试配置 ───────────────────────────────────────────[tool.pytest.ini_options]testpaths = ["tests"]python_files = ["test_*.py", "*_test.py"]addopts = [ "--tb=short", # 简洁的失败回溯 "--cov=src", # 覆盖率(需要 pytest-cov) "--cov-report=term-missing",]
# ── coverage.py 覆盖率配置 ───────────────────────────────────[tool.coverage.run]source = ["src"]omit = ["tests/*", "**/__pycache__/*"]
[tool.coverage.report]show_missing = trueskip_covered = false四、推荐项目结构(src layout)
my-project/│├── pyproject.toml # 统一配置(见第三节)├── uv.lock # 精确依赖 lock 文件(自动生成,提交到 git)├── README.md├── LICENSE├── .python-version # 项目使用的 Python 版本(uv python pin 生成)│├── src/ # ← src layout 的核心:包放在 src/ 下│ └── my_project/ # 实际的 Python 包│ ├── __init__.py│ ├── main.py│ ├── cli.py # CLI 入口│ ├── models/│ │ ├── __init__.py│ │ └── user.py│ └── utils/│ ├── __init__.py│ └── helpers.py│├── tests/│ ├── conftest.py # pytest fixtures│ ├── test_main.py│ └── utils/│ └── test_helpers.py│└── .github/ └── workflows/ └── ci.yml # GitHub Actions CI为什么用 src layout?
# 传统 flat layout(包直接放项目根目录)的问题:# my_project/ ← 与项目根目录同名# tests/
# 在项目根目录运行 pytest 时:import my_project # Python 找到的是项目根目录下的 my_project/ # 而不是已安装的包,导致测试通过但实际安装失败!
# src layout 完全避免这个问题:# src/ 目录不在 Python 路径中,必须通过 pip install -e . 安装才能 import# 这确保测试环境和实际安装环境完全一致五、Ruff:一个工具替代全套 lint 工具链
5.1 安装与基础使用
# 通过 uv 安装到开发依赖uv add --dev ruff
# 或独立安装(不依赖 Python 环境)pip install ruff# Lint 检查(报告问题,不修改文件)ruff check src/ tests/
# Lint + 自动修复(修复可自动修复的问题)ruff check --fix src/ tests/
# 格式化(等价于 black)ruff format src/ tests/
# 检查 + 格式化(CI 中最常用)ruff check src/ && ruff format --check src/5.2 常见 Ruff 规则示例
# ── F401:未使用的 import ─────────────────────────────────────import os # ← F401 如果 os 未在文件中使用import sys
# ── I001:import 排序(isort 规则)──────────────────────────# 错误:第三方库和标准库混排import requestsimport os
# 正确(ruff format 自动修复)import osimport requests
# ── UP007:使用更简洁的 Union 写法(Python 3.10+)──────────────from typing import Union, Optional
def func(x: Union[int, str]) -> Optional[str]: # UP007: 建议改为 int | str ...
# ruff --fix 自动改为:def func(x: int | str) -> str | None: ...
# ── B006:可变默认参数(常见 bug!)──────────────────────────def bad_function(data: list = []): # B006: 默认列表是共享对象! data.append(1) return data
def good_function(data: list | None = None): # ✅ if data is None: data = [] data.append(1) return data
# ── SIM108:三元表达式简化 ──────────────────────────────────# 建议:if condition: x = aelse: x = b
# 自动改为:x = a if condition else b5.3 与 Black/flake8 迁移对比
# 原来的工具链(.pre-commit-config.yaml)# - repo: https://github.com/psf/black# hooks: [{ id: black }]# - repo: https://github.com/pycqa/isort# hooks: [{ id: isort }]# - repo: https://github.com/pycqa/flake8# hooks: [{ id: flake8 }]# 三个工具串行执行,约需 20-40 秒
# 改为 Ruff(pre-commit 配置):# - repo: https://github.com/astral-sh/ruff-pre-commit# rev: v0.5.0# hooks:# - id: ruff # lint + fix# args: [--fix]# - id: ruff-format # format# 单一工具,约需 0.5-2 秒六、从零创建项目:完整流程
# 1. 初始化项目uv init --lib my-project # --lib 使用 src layoutcd my-project
# 2. 固定 Python 版本uv python pin 3.12
# 3. 添加依赖uv add requests pydantic httpxuv add --dev pytest pytest-cov ruff mypy
# 4. 配置(编辑 pyproject.toml,参考第三节模板)
# 5. 检查配置uv sync # 安装所有依赖uv run ruff check src/ # 运行 lint 检查uv run ruff format src/ # 格式化代码uv run mypy src/ # 类型检查uv run pytest # 运行测试
# 6. 查看生成的依赖 lock 文件cat uv.lock # 包含所有依赖的精确版本(提交到 git)
# 7. 开发模式安装(使代码可以被 import)uv pip install -e .一键初始化脚本
#!/bin/bash# bootstrap.sh —— 快速初始化新 Python 项目
PROJECT_NAME=${1:-"my-project"}
# 初始化项目uv init --lib "$PROJECT_NAME"cd "$PROJECT_NAME"
# 固定 Python 3.12uv python pin 3.12
# 添加开发依赖uv add --dev \ pytest>=8.0 \ pytest-cov>=5.0 \ ruff>=0.5 \ mypy>=1.10
# 开发模式安装uv pip install -e .
echo "✅ 项目 $PROJECT_NAME 初始化完成!"echo "📂 目录结构:"find . -not -path './.venv/*' -not -path './.git/*' | sort | head -30七、CI/CD 集成(GitHub Actions)
name: CI
on: push: branches: [main] pull_request: branches: [main]
jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.11", "3.12"]
steps: - uses: actions/checkout@v4
- name: Install uv uses: astral-sh/setup-uv@v3 with: enable-cache: true # 启用 GitHub Actions 缓存
- name: Set up Python run: uv python install ${{ matrix.python-version }}
- name: Install dependencies run: uv sync --frozen # 严格使用 lock 文件,不允许更新
- name: Lint with Ruff run: | uv run ruff check src/ tests/ uv run ruff format --check src/ tests/
- name: Type check with mypy run: uv run mypy src/
- name: Run tests run: uv run pytest --cov --cov-report=xml
- name: Upload coverage uses: codecov/codecov-action@v4 with: file: ./coverage.xml八、工具链对比总结
| 功能 | 老式工具 | 现代替代 | 优势 |
|---|---|---|---|
| 包安装 | pip | uv | 10-100x 更快,全局缓存 |
| 虚拟环境 | venv / virtualenv | uv venv | 自动集成,无需单独管理 |
| 依赖 lock | pip-tools + requirements.txt | uv.lock(自动) | 自动生成,精确可复现 |
| 依赖管理 | requirements.txt(手动) | pyproject.toml + uv add | 分组管理,自动更新 |
| Linting | flake8 | Ruff | 更快(Rust)+ 规则更多 |
| Import 排序 | isort | Ruff(I 规则) | 合并到单一工具 |
| 代码格式化 | Black | Ruff format | 兼容 Black,更快 |
| 类型检查 | mypy | mypy(继续使用) | 无需替换 |
| 项目配置 | setup.py + 多个 .ini | pyproject.toml | 单文件统管 |
下一篇预告
Python 项目工程化实战(二):测试体系建设——pytest fixtures 进阶、参数化测试、mock/patch 最佳实践、测试覆盖率要求与 CI 门槛设置。
相关文章:
- Python 类型注解完全指南:从基础标注到 Pydantic 数据验证
- Python 性能优化实战:cProfile + NumPy 向量化 + functools.cache
- Python 并发编程完全指南:GIL / threading / multiprocessing
- Python asyncio 异步编程完全指南
本文所有工具版本信息以 2026 年 7 月为准,uv/Ruff 更新较快,请以官方文档为准。
Python 项目工程化实战(一):pyproject.toml + uv + Ruff 现代工具链从零搭建 | Python 进阶
https://971918.xyz/posts/python-guide/python-project-engineering-01/