Python项目工程化实战手册 - 从零构建专业开发环境

本文关于Python项目工程化的文档虽然详尽,但往往让人困惑于各种工具链的复杂配置,难以理解每个依赖的实际作用。为了解决这个问题,本文将通过一份完整的实操手册,带你从零开始,亲手创建一个真实的Python项目。我们不仅会配置完整的开发工具链,还会详细解释每一步做什么、为什么这样做、以及还有哪些选择,让你通过实践真正掌握现代Python项目的构建与管理亲手创建一个真实的Python项目。

一、准备工作

1.1 你需要准备什么

在开始之前,请确保你有:

工具 用途 安装方式
Git 版本控制 https://git-scm.com/downloads
VS Code 代码编辑器 https://code.visualstudio.com/
Python 3.11+ 编程语言 https://www.python.org/downloads/
GitHub账号 代码托管 https://github.com/signup

1.2 安装uv

什么是uv?
uv是Astral公司开发的Python包管理器,它比pip快10-100倍,可以替代pip、pip-tools、virtualenv、poetry等工具。

为什么选择uv而不是Poetry/pip?

  • 速度极快:Rust编写,比pip快10-100倍
  • 功能全面:集成虚拟环境管理、依赖解析、锁文件
  • 兼容性好:完全兼容pip和pyproject.toml标准
  • 简单易用:命令直观,学习成本低

安装uv:

1
2
3
4
5
6
7
8
# Windows PowerShell(推荐)
irm https://astral.sh/uv/install.ps1 | iex

# 或使用pip安装
pip install uv

# 验证安装
uv --version

拓展:其他Python包管理工具对比

  • pip:Python官方,功能基础,速度慢
  • Poetry:功能强大,但配置复杂,速度一般
  • PDM:支持PEP标准,国产工具
  • uv:速度最快,功能完善,推荐新项目使用

1.3 配置Git

如果你还没有配置Git,执行以下命令:

1
2
3
4
5
6
7
8
9
10
11
12
# 配置用户名和邮箱(必须,用于标识提交者)
git config --global user.name "你的名字"
git config --global user.email "你的邮箱@example.com"

# 配置默认分支名为main(可选,GitHub默认使用main)
git config --global init.defaultBranch main

# 配置换行符处理(Windows推荐)
git config --global core.autocrlf true

# 验证配置
git config --list

为什么需要配置用户名和邮箱?
Git的每次提交都需要记录”谁”在”什么时候”做了”什么修改”。用户名和邮箱就是”谁”的标识。


二、创建远程Git仓库

2.1 在GitHub上创建仓库

  1. 打开 https://github.com/new
  2. 填写仓库信息:
    • Repository namepython-demo(你可以自定义)
    • Description:Python工程化示例项目
    • Visibility:Public(公开)或 Private(私有)
    • 不要勾选 “Add a README file”(我们会在本地创建)
    • 不要勾选 “Add .gitignore”
    • 不要勾选 “Choose a license”
  3. 点击 Create repository

创建完成后,你会看到一个空仓库页面,记住仓库地址,类似:

1
https://github.com/你的用户名/python-demo.git

为什么不在GitHub上初始化文件?
因为我们要在本地创建项目,如果GitHub上已有文件,推送时会产生冲突。保持GitHub仓库为空,可以让我们直接推送本地内容。

拓展:GitHub vs GitLab vs Gitee

  • GitHub:全球最大,开源生态最好,国内访问可能慢
  • GitLab:功能强大,可自建,企业常用
  • Gitee:国内访问快,但生态较小

本教程使用GitHub,但流程在其他平台也基本相同。

2.2 配置SSH密钥(推荐)

使用SSH可以免密码推送代码,更安全更方便。

步骤1:检查是否已有SSH密钥

1
2
3
4
# 查看是否存在SSH密钥
ls ~/.ssh/id_ed25519.pub
# 或
ls ~/.ssh/id_rsa.pub

如果文件存在,跳到步骤3。

步骤2:生成SSH密钥

1
2
3
4
# 生成新的SSH密钥(推荐Ed25519算法)
ssh-keygen -t ed25519 -C "你的邮箱@example.com"

# 按3次回车(使用默认路径,不设置密码)

步骤3:添加SSH密钥到GitHub

1
2
3
4
# 复制公钥内容
cat ~/.ssh/id_ed25519.pub
# 或者用剪贴板
Get-Content ~/.ssh/id_ed25519.pub | Set-Clipboard

然后:

  1. 打开 https://github.com/settings/keys
  2. 点击 New SSH key
  3. Title填写:My PC(或任何你能识别的名字)
  4. Key粘贴刚才复制的内容
  5. 点击 Add SSH key

步骤4:测试连接

1
ssh -T git@github.com

如果看到 Hi 用户名! You've successfully authenticated...,说明配置成功。

为什么推荐SSH而不是HTTPS?

  • HTTPS每次推送都需要输入密码(或配置令牌)
  • SSH配置一次后永久免密
  • SSH更安全,密钥保存在本地

三、本地项目初始化

3.1 创建项目目录

1
2
3
4
5
6
7
8
9
# 进入你想存放项目的目录(例如 D:\projects)
cd D:\projects

# 创建项目文件夹
mkdir python-demo
cd python-demo

# 查看当前路径
pwd

3.2 初始化Git仓库

1
2
3
4
5
# 初始化Git仓库
git init

# 查看状态
git status

你会看到:

1
2
3
On branch main
No commits yet
nothing to commit (create/copy files and use "git add" to track)

这一步做了什么?
git init 在当前目录创建了一个隐藏的 .git 文件夹,这个文件夹存储了Git的所有版本历史和配置信息。从此刻起,这个目录就是一个Git仓库了。

3.3 使用uv初始化Python项目

1
2
3
4
5
# 使用uv初始化项目
uv init

# 指定Python版本(可选,如果你想使用特定版本)
uv python pin 3.11

这会创建以下文件:

1
2
3
4
5
python-demo/
├── .python-version # Python版本锁定
├── pyproject.toml # 项目配置文件
├── README.md # 项目说明
└── main.py # 示例代码

为什么需要pyproject.toml?
pyproject.toml 是Python的标准项目配置文件(PEP 518/621),用于:

  • 定义项目元信息(名称、版本、作者等)
  • 管理依赖
  • 配置各种开发工具(black、mypy、pytest等)

它替代了以前的 setup.pysetup.cfgrequirements.txt 等多个文件。

3.4 创建项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
# 创建源代码目录
mkdir src
mkdir src/python_demo

# 创建测试目录
mkdir tests

# 创建空的__init__.py文件(使目录成为Python包)
New-Item -Path src/python_demo/__init__.py -ItemType File
New-Item -Path tests/__init__.py -ItemType File

# 删除uv创建的示例文件
Remove-Item hello.py

现在的项目结构:

1
2
3
4
5
6
7
8
9
python-demo/
├── .python-version
├── pyproject.toml
├── README.md
├── src/
│ └── python_demo/
│ └── __init__.py
└── tests/
└── __init__.py

为什么使用 src 目录结构?
这是Python社区推荐的项目结构(src layout),好处是:

  • 避免测试时意外导入本地未安装的包
  • 明确区分源代码和其他文件
  • 更容易配置打包和发布

拓展:项目结构的两种流派

1
2
3
4
5
6
7
8
9
10
11
12
# Flat layout(扁平结构)
python_demo/
├── python_demo/ # 包直接在根目录
│ └── __init__.py
└── tests/

# Src layout(src结构,推荐)
python_demo/
├── src/
│ └── python_demo/ # 包在src目录下
│ └── __init__.py
└── tests/

3.5 编辑pyproject.toml

用VS Code打开项目:

1
code .

打开 pyproject.toml,根据你选择的工具链方案进行配置。

方案选择:Ruff vs 传统工具链

在配置之前,你需要选择一种代码质量工具方案:

对比项 方案A:Ruff(推荐) 方案B:Black + isort + flake8
工具数量 1个 3个
执行速度 极快(Rust编写) 较慢(Python编写)
配置复杂度 简单(统一配置) 较复杂(多工具配置)
功能覆盖 格式化 + 导入排序 + lint 格式化 + 导入排序 + lint
社区成熟度 新兴但发展迅速 成熟稳定
适用场景 新项目、追求效率 已有项目、团队习惯

方案A:使用Ruff(推荐)

Ruff是All-in-one工具,同时替代Black、isort、flake8、pyupgrade等多个工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
[project]
name = "python-demo"
version = "0.1.0"
description = "Python工程化示例项目"
readme = "README.md"
requires-python = ">=3.11"
dependencies = []

[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-cov>=4.1.0",
"mypy>=1.8.0",
"ruff>=0.3.0",
"pre-commit>=3.6.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/python_demo"]

# ============================================
# Ruff 配置(替代 Black + isort + flake8)
# ============================================
[tool.ruff]
# 目标Python版本
target-version = "py311"

# 行长度限制
line-length = 120

# 要检查的目录
src = ["src", "tests"]

# 排除的目录
exclude = [
".git",
".venv",
"__pycache__",
"build",
"dist",
]

[tool.ruff.lint]
# 启用的规则集
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
]

# 忽略的规则
ignore = [
"E501", # 行太长(由formatter处理)
]

[tool.ruff.lint.isort]
# isort配置
known-first-party = ["python_demo"]

[tool.ruff.format]
# 格式化配置(类似Black)
quote-style = "double"
indent-style = "space"
line-ending = "auto"

# ============================================
# MyPy 配置
# ============================================
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
ignore_missing_imports = true

# ============================================
# Pytest 配置
# ============================================
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = [
"-v",
"--tb=short",
]

# ============================================
# Coverage 配置
# ============================================
[tool.coverage.run]
source = ["src"]
branch = true

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
"if __name__ == .__main__.:",
]

Ruff常用命令:

1
2
3
4
5
6
7
8
9
10
11
# 代码检查
uv run ruff check .

# 代码检查并自动修复
uv run ruff check --fix .

# 代码格式化
uv run ruff format .

# 检查格式(不修改)
uv run ruff format --check .

方案B:使用Black + isort + flake8(传统方案)

如果你更熟悉传统工具链,或者团队已有使用习惯,可以选择这个方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
[project]
name = "python-demo"
version = "0.1.0"
description = "Python工程化示例项目"
readme = "README.md"
requires-python = ">=3.11"
dependencies = []

[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-cov>=4.1.0",
"mypy>=1.8.0",
"black>=24.0.0",
"isort>=5.13.0",
"flake8>=7.0.0",
"flake8-bugbear>=24.0.0",
"pre-commit>=3.6.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/python_demo"]

# ============================================
# Black 配置(代码格式化)
# ============================================
[tool.black]
# 行长度限制
line-length = 120

# 目标Python版本
target-version = ["py311"]

# 包含的目录
include = '\.pyi?$'

# 排除的目录
exclude = '''
/(
\.git
| \.venv
| __pycache__
| build
| dist
)/
'''

# ============================================
# isort 配置(导入排序)
# ============================================
[tool.isort]
# 使用Black兼容模式
profile = "black"

# 行长度(与Black保持一致)
line_length = 120

# 已知的第一方模块
known_first_party = ["python_demo"]

# 源码目录
src_paths = ["src", "tests"]

# 跳过的目录
skip = [".git", ".venv", "__pycache__", "build", "dist"]

# 导入分组顺序
sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]

# ============================================
# flake8 配置
# 注意:flake8不支持pyproject.toml,需要创建.flake8文件
# 这里的配置仅作参考,实际需要在.flake8中配置
# ============================================

# ============================================
# MyPy 配置
# ============================================
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
ignore_missing_imports = true

# ============================================
# Pytest 配置
# ============================================
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = [
"-v",
"--tb=short",
]

# ============================================
# Coverage 配置
# ============================================
[tool.coverage.run]
source = ["src"]
branch = true

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
"if __name__ == .__main__.:",
]

方案B额外配置:创建.flake8文件

由于flake8不支持pyproject.toml,需要在项目根目录创建 .flake8 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[flake8]
# 行长度(与Black保持一致)
max-line-length = 120

# 排除的目录
exclude =
.git,
.venv,
__pycache__,
build,
dist

# 忽略的规则
ignore =
# E501: 行太长(由Black处理)
E501,
# W503: 二元运算符前换行(与Black风格冲突)
W503,
# E203: 冒号前空格(与Black风格冲突)
E203

# 每个文件的最大复杂度
max-complexity = 10

# 启用的扩展
extend-select = B,B9

方案B常用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 代码格式化
uv run black .

# 检查格式(不修改)
uv run black --check .

# 导入排序
uv run isort .

# 检查导入排序(不修改)
uv run isort --check-only .

# 代码检查
uv run flake8 .

两种方案的功能对应关系

功能 Ruff命令 传统工具命令
代码格式化 ruff format . black .
检查格式 ruff format --check . black --check .
导入排序 ruff check --select I --fix . isort .
代码检查 ruff check . flake8 .
自动修复 ruff check --fix . 需手动修复

建议:新项目推荐使用方案A(Ruff),配置简单、速度快、功能完整。如果是已有项目或团队已有使用Black/isort/flake8的习惯,可以继续使用方案B

3.6 安装开发依赖

1
2
3
4
5
6
7
# 创建虚拟环境并安装依赖
uv sync --all-extras

# 这会自动:
# 1. 创建 .venv 虚拟环境
# 2. 安装所有依赖(包括dev依赖)
# 3. 生成 uv.lock 锁文件

验证安装(根据你选择的方案):

方案A:Ruff

1
2
3
4
5
6
7
8
# 激活虚拟环境(Windows PowerShell)
.\.venv\Scripts\Activate.ps1

# 验证工具已安装
ruff --version
mypy --version
pytest --version
pre-commit --version

方案B:Black + isort + flake8

1
2
3
4
5
6
7
8
9
10
# 激活虚拟环境(Windows PowerShell)
.\.venv\Scripts\Activate.ps1

# 验证工具已安装
black --version
isort --version
flake8 --version
mypy --version
pytest --version
pre-commit --version

什么是锁文件(uv.lock)?
锁文件记录了所有依赖的精确版本,确保团队成员和CI环境安装完全相同的依赖版本。


四、配置开发工具链

4.1 创建.gitignore

创建 .gitignore 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# 虚拟环境
.venv/
venv/
ENV/

# IDE
.idea/
.vscode/
*.swp
*.swo

# 测试和覆盖率
.coverage
htmlcov/
.pytest_cache/
.mypy_cache/
.ruff_cache/

# 系统文件
.DS_Store
Thumbs.db

# 项目特定
*.log
*.tmp

为什么需要.gitignore?
有些文件不应该被提交到Git仓库:

  • 自动生成的文件(__pycache__
  • 本地环境相关的文件(.venv
  • 包含敏感信息的文件
  • IDE配置文件(可选)

4.2 更新README.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Python Demo

Python工程化示例项目,演示现代Python项目的最佳实践。

## 功能特性

- 使用uv管理依赖
- Ruff代码检查和格式化
- MyPy类型检查
- Pytest单元测试
- Pre-commit自动化检查
- GitHub Actions CI/CD

## 快速开始

### 环境要求

- Python 3.11+
- uv

### 安装

```bash
# 克隆项目
git clone https://github.com/你的用户名/python-demo.git
cd python-demo

# 安装依赖
uv sync --all-extras

# 激活虚拟环境
# Windows
.\.venv\Scripts\Activate.ps1
# Linux/macOS
source .venv/bin/activate

开发

1
2
3
4
5
6
7
8
9
10
11
# 运行测试
uv run pytest

# 代码检查
uv run ruff check .

# 代码格式化
uv run ruff format .

# 类型检查
uv run mypy src/

项目结构

python-demo/
├── src/
│ └── python_demo/ # 源代码
├── tests/ # 测试代码
├── pyproject.toml # 项目配置
└── README.md

许可证

MIT

4.3 验证工具是否正常工作

根据你选择的方案,验证工具是否正常工作:

方案A:Ruff版验证命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 确保在虚拟环境中
.\.venv\Scripts\Activate.ps1

# 1. Ruff检查(目前没有代码,应该没有错误)
uv run ruff check .

# 2. Ruff格式化(检查模式)
uv run ruff format --check .

# 3. MyPy(目前没有代码)
uv run mypy src/

# 4. Pytest(目前没有测试)
uv run pytest

方案B:传统工具链验证命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 确保在虚拟环境中
.\.venv\Scripts\Activate.ps1

# 1. Black格式检查
uv run black --check .

# 2. isort导入排序检查
uv run isort --check-only .

# 3. flake8代码检查
uv run flake8 .

# 4. MyPy类型检查
uv run mypy src/

# 5. Pytest测试
uv run pytest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---

## 五、VS Code配置

### 5.1 安装推荐扩展

根据你选择的工具方案,安装对应的VS Code扩展:

#### 方案A:Ruff版扩展

| 扩展 | ID | 用途 |
|------|-----|------|
| Python | ms-python.python | Python基础支持 |
| Pylance | ms-python.vscode-pylance | 类型检查和智能提示 |
| Ruff | charliermarsh.ruff | Ruff集成 |
| Even Better TOML | tamasfe.even-better-toml | TOML语法高亮 |

安装命令:

```powershell
code --install-extension ms-python.python
code --install-extension ms-python.vscode-pylance
code --install-extension charliermarsh.ruff
code --install-extension tamasfe.even-better-toml

方案B:传统工具链扩展

扩展 ID 用途
Python ms-python.python Python基础支持
Pylance ms-python.vscode-pylance 类型检查和智能提示
Black Formatter ms-python.black-formatter Black格式化
isort ms-python.isort 导入排序
Flake8 ms-python.flake8 代码检查
Even Better TOML tamasfe.even-better-toml TOML语法高亮

安装命令:

1
2
3
4
5
6
code --install-extension ms-python.python
code --install-extension ms-python.vscode-pylance
code --install-extension ms-python.black-formatter
code --install-extension ms-python.isort
code --install-extension ms-python.flake8
code --install-extension tamasfe.even-better-toml

5.2 创建工作区配置

根据你选择的方案,创建 .vscode/settings.json

方案A:Ruff版settings.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
// Python配置
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/Scripts/python.exe",

// 使用Ruff作为格式化工具
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit"
}
},

// Ruff配置
"ruff.configurationPreference": "filesystemFirst",

// Pylance配置
"python.analysis.typeCheckingMode": "basic",
"python.analysis.autoImportCompletions": true,

// 编辑器配置
"editor.rulers": [120],
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true
}

方案B:Black + isort版settings.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
// Python配置
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/Scripts/python.exe",

// 使用Black作为格式化工具
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},

// Black配置(使用pyproject.toml中的配置)
"black-formatter.args": ["--config", "pyproject.toml"],

// isort配置(使用pyproject.toml中的配置)
"isort.args": ["--settings-path", "pyproject.toml"],

// flake8配置(使用.flake8中的配置)
"flake8.args": [],

// Pylance配置
"python.analysis.typeCheckingMode": "basic",
"python.analysis.autoImportCompletions": true,

// 编辑器配置
"editor.rulers": [120],
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true
}

5.3 创建推荐扩展配置

根据你的方案,创建 .vscode/extensions.json

方案A:Ruff版extensions.json

1
2
3
4
5
6
7
8
{
"recommendations": [
"ms-python.python",
"ms-python.vscode-pylance",
"charliermarsh.ruff",
"tamasfe.even-better-toml"
]
}

方案B:传统工具链版extensions.json

1
2
3
4
5
6
7
8
9
10
{
"recommendations": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.black-formatter",
"ms-python.isort",
"ms-python.flake8",
"tamasfe.even-better-toml"
]
}

为什么需要这个文件?
当团队成员打开项目时,VS Code会提示安装推荐的扩展,确保团队使用统一的开发环境。

5.4 选择Python解释器

  1. Ctrl+Shift+P 打开命令面板
  2. 输入 Python: Select Interpreter
  3. 选择 .venv 中的Python解释器

六、编写示例代码

现在让我们编写一些实际的代码,来验证我们的工具链。

6.1 创建核心模块

创建 src/python_demo/calculator.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""简单计算器模块。

这个模块提供基本的数学运算功能,用于演示Python项目工程化。
"""

from typing import Union

Number = Union[int, float]


def add(a: Number, b: Number) -> Number:
"""两数相加。

Args:
a: 第一个数
b: 第二个数

Returns:
两数之和

Examples:
>>> add(1, 2)
3
>>> add(1.5, 2.5)
4.0
"""
return a + b


def subtract(a: Number, b: Number) -> Number:
"""两数相减。

Args:
a: 被减数
b: 减数

Returns:
两数之差
"""
return a - b


def multiply(a: Number, b: Number) -> Number:
"""两数相乘。

Args:
a: 第一个数
b: 第二个数

Returns:
两数之积
"""
return a * b


def divide(a: Number, b: Number) -> float:
"""两数相除。

Args:
a: 被除数
b: 除数

Returns:
两数之商

Raises:
ValueError: 当除数为0时抛出
"""
if b == 0:
raise ValueError("除数不能为0")
return a / b


class Calculator:
"""计算器类,支持链式运算。

Attributes:
result: 当前计算结果

Examples:
>>> calc = Calculator(10)
>>> calc.add(5).multiply(2).result
30
"""

def __init__(self, initial_value: Number = 0) -> None:
"""初始化计算器。

Args:
initial_value: 初始值,默认为0
"""
self.result: Number = initial_value

def add(self, value: Number) -> "Calculator":
"""加法运算。

Args:
value: 要加的值

Returns:
返回self以支持链式调用
"""
self.result = add(self.result, value)
return self

def subtract(self, value: Number) -> "Calculator":
"""减法运算。

Args:
value: 要减的值

Returns:
返回self以支持链式调用
"""
self.result = subtract(self.result, value)
return self

def multiply(self, value: Number) -> "Calculator":
"""乘法运算。

Args:
value: 要乘的值

Returns:
返回self以支持链式调用
"""
self.result = multiply(self.result, value)
return self

def divide(self, value: Number) -> "Calculator":
"""除法运算。

Args:
value: 要除的值

Returns:
返回self以支持链式调用

Raises:
ValueError: 当除数为0时抛出
"""
self.result = divide(self.result, value)
return self

def reset(self, value: Number = 0) -> "Calculator":
"""重置计算器。

Args:
value: 重置后的值,默认为0

Returns:
返回self以支持链式调用
"""
self.result = value
return self

6.2 更新__init__.py

更新 src/python_demo/__init__.py

1
2
3
4
5
6
7
8
9
"""Python Demo - Python工程化示例项目。

这个包提供简单的计算器功能,用于演示Python项目的工程化实践。
"""

from python_demo.calculator import Calculator, add, divide, multiply, subtract

__version__ = "0.1.0"
__all__ = ["Calculator", "add", "subtract", "multiply", "divide"]

6.3 编写测试

创建 tests/test_calculator.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""计算器模块测试。"""

import pytest

from python_demo.calculator import Calculator, add, divide, multiply, subtract


class TestBasicOperations:
"""基本运算函数测试。"""

def test_add_integers(self) -> None:
"""测试整数加法。"""
assert add(1, 2) == 3
assert add(-1, 1) == 0
assert add(0, 0) == 0

def test_add_floats(self) -> None:
"""测试浮点数加法。"""
assert add(1.5, 2.5) == 4.0
assert add(0.1, 0.2) == pytest.approx(0.3)

def test_subtract(self) -> None:
"""测试减法。"""
assert subtract(5, 3) == 2
assert subtract(3, 5) == -2
assert subtract(0, 0) == 0

def test_multiply(self) -> None:
"""测试乘法。"""
assert multiply(3, 4) == 12
assert multiply(-2, 3) == -6
assert multiply(0, 100) == 0

def test_divide(self) -> None:
"""测试除法。"""
assert divide(10, 2) == 5.0
assert divide(7, 2) == 3.5
assert divide(-10, 2) == -5.0

def test_divide_by_zero(self) -> None:
"""测试除以零的情况。"""
with pytest.raises(ValueError, match="除数不能为0"):
divide(10, 0)


class TestCalculator:
"""计算器类测试。"""

def test_initial_value(self) -> None:
"""测试初始值。"""
calc = Calculator()
assert calc.result == 0

calc = Calculator(10)
assert calc.result == 10

def test_chain_operations(self) -> None:
"""测试链式运算。"""
calc = Calculator(10)
result = calc.add(5).multiply(2).subtract(10).divide(2).result
assert result == 10.0

def test_reset(self) -> None:
"""测试重置功能。"""
calc = Calculator(100)
calc.add(50).reset()
assert calc.result == 0

calc.reset(42)
assert calc.result == 42


# 参数化测试示例
@pytest.mark.parametrize(
"a, b, expected",
[
(1, 2, 3),
(0, 0, 0),
(-1, -1, -2),
(100, 200, 300),
(1.5, 2.5, 4.0),
],
)
def test_add_parametrized(a: float, b: float, expected: float) -> None:
"""参数化测试:加法。"""
assert add(a, b) == expected

6.4 验证代码

根据你选择的方案,运行工具来检查代码:

方案A:Ruff版验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 代码检查
uv run ruff check .
# 如果有问题,自动修复
uv run ruff check --fix .

# 2. 代码格式化
uv run ruff format .

# 3. 类型检查
uv run mypy src/

# 4. 运行测试
uv run pytest

# 5. 运行测试并生成覆盖率报告
uv run pytest --cov=src --cov-report=term-missing

方案B:传统工具链验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 代码格式化
uv run black .

# 2. 导入排序
uv run isort .

# 3. 代码检查
uv run flake8 .

# 4. 类型检查
uv run mypy src/

# 5. 运行测试
uv run pytest

# 6. 运行测试并生成覆盖率报告
uv run pytest --cov=src --cov-report=term-missing

命令对照表:

功能 Ruff(方案A) 传统工具链(方案B)
代码检查 ruff check . flake8 .
自动修复 ruff check --fix . 需手动修复
代码格式化 ruff format . black .
导入排序 包含在 ruff check isort .
类型检查 mypy src/ mypy src/
运行测试 pytest pytest

七、pre-commit配置

7.1 什么是pre-commit?

pre-commit是一个Git钩子管理框架。它可以在你执行git commit之前自动运行一系列检查。

为什么需要它?

  • 强制质量检查:忘记手动检查?pre-commit会自动检查
  • 团队一致性:确保所有人提交的代码都经过相同的检查
  • 节省CI时间:本地先检查,减少CI失败

工作原理:

当你运行 pre-commit install 后,它会在 .git/hooks/ 目录下创建脚本文件。Git 在执行 commitpush 时会自动调用这些脚本。

7.2 理解配置文件结构

.pre-commit-config.yaml 是 pre-commit 的核心配置文件,理解它的结构非常重要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ============================================
# 全局配置
# ============================================
fail_fast: true # 第一个 hook 失败后停止
default_stages: [pre-commit] # 所有 hooks 默认运行的阶段

# ============================================
# repos:从哪里获取 hooks
# ============================================
repos:
# 远程仓库:自动下载、隔离环境
- repo: https://github.com/psf/black
rev: 24.3.0 # 版本锁定
hooks:
- id: black # hook 的唯一标识符

# 本地仓库:使用项目环境
- repo: local
hooks:
- id: pytest
entry: uv run pytest # 执行命令
stages: [pre-push] # 覆盖 default_stages

核心概念:

配置项 作用
repos 列表,定义从哪些来源获取 hooks
repo 远程 Git 仓库 URL 或 local
rev 仓库版本(标签/commit),用于锁定版本
id hook 的唯一标识,由仓库定义
stages 指定运行阶段,覆盖 default_stages
additional_dependencies 补充安装额外依赖

重要提示: pre-commit 使用独立的隔离环境(存储在 ~/.cache/pre-commit/),与项目虚拟环境完全分离。远程仓库的工具会自动下载安装,确保团队成员使用完全相同版本

多个 repo 的作用:

每个 repo 代表一个 hooks 来源(类似”应用商店”)。不同工具由不同团队维护,所以需要从多个 repo 获取:

1
2
3
4
5
6
7
8
9
10
11
repos:
# 从"通用工具商店"获取多个小工具
- repo: https://github.com/pre-commit/pre-commit-hooks
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer

# 从"Black官方店"获取格式化工具
- repo: https://github.com/psf/black
hooks:
- id: black

执行顺序: 同一阶段的 hooks 按定义顺序执行,所以格式化工具应放在检查工具之前。

7.3 创建pre-commit配置

根据你在 3.5 节选择的工具方案,创建对应的 .pre-commit-config.yaml


方案A:Ruff版pre-commit配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# Pre-commit配置文件(Ruff版)
# 文档:https://pre-commit.com

# 第一个hook失败后停止运行后续hooks
fail_fast: true

# 默认阶段
default_stages: [pre-commit]

repos:
# ============================================
# 通用检查
# ============================================
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
# 删除行尾空格
- id: trailing-whitespace
# 确保文件以换行符结尾
- id: end-of-file-fixer
# 检查YAML语法
- id: check-yaml
# 检查TOML语法
- id: check-toml
# 检查JSON语法
- id: check-json
# 检查合并冲突标记
- id: check-merge-conflict
# 检测私钥文件
- id: detect-private-key
# 检查大文件
- id: check-added-large-files
args: ['--maxkb=1000']

# ============================================
# Ruff - 代码检查和格式化
# ============================================
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.0
hooks:
# Ruff检查(替代flake8)
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
# Ruff格式化(替代black)
- id: ruff-format

# ============================================
# MyPy - 类型检查
# ============================================
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: []
args: [--config-file, pyproject.toml]

# ============================================
# 本地hooks
# ============================================
- repo: local
hooks:
# 推送前运行测试
- id: pytest
name: pytest
entry: uv run pytest -x -q
language: system
types: [python]
stages: [pre-push] # 只在push时运行
pass_filenames: false

方案B:Black + isort + flake8版pre-commit配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# Pre-commit配置文件(传统工具链版)
# 文档:https://pre-commit.com

# 第一个hook失败后停止运行后续hooks
fail_fast: true

# 默认阶段
default_stages: [pre-commit]

repos:
# ============================================
# 通用检查
# ============================================
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
# 删除行尾空格
- id: trailing-whitespace
# 确保文件以换行符结尾
- id: end-of-file-fixer
# 检查YAML语法
- id: check-yaml
# 检查TOML语法
- id: check-toml
# 检查JSON语法
- id: check-json
# 检查合并冲突标记
- id: check-merge-conflict
# 检测私钥文件
- id: detect-private-key
# 检查大文件
- id: check-added-large-files
args: ['--maxkb=1000']

# ============================================
# Black - 代码格式化
# ============================================
- repo: https://github.com/psf/black
rev: 24.2.0
hooks:
- id: black
args: [--config, pyproject.toml]

# ============================================
# isort - 导入排序
# ============================================
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
args: [--settings-path, pyproject.toml]

# ============================================
# flake8 - 代码检查
# ============================================
- repo: https://github.com/pycqa/flake8
rev: 7.0.0
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear>=24.0.0
# flake8使用.flake8配置文件

# ============================================
# MyPy - 类型检查
# ============================================
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: []
args: [--config-file, pyproject.toml]

# ============================================
# 本地hooks
# ============================================
- repo: local
hooks:
# 推送前运行测试
- id: pytest
name: pytest
entry: uv run pytest -x -q
language: system
types: [python]
stages: [pre-push] # 只在push时运行
pass_filenames: false

注意:方案B的pre-commit会依次运行Black → isort → flake8,确保代码先格式化再检查。

7.4 安装pre-commit钩子

1
2
3
4
5
6
7
8
# 安装pre-commit钩子(在 .git/hooks/ 创建 pre-commit 脚本)
uv run pre-commit install

# 安装pre-push钩子(在 .git/hooks/ 创建 pre-push 脚本)
uv run pre-commit install --hook-type pre-push

# 验证安装
cat .git/hooks/pre-commit

pre-commit install 做了什么?

这个命令把 pre-commit 框架”挂载”到 Git 的提交流程中。安装后,每次 git commit 时,Git 会自动调用 .git/hooks/pre-commit 脚本。

支持的钩子类型(共 8 种):

钩子类型 触发时机 典型用途
pre-commit git commit 代码格式化、lint 检查
pre-push git push 运行测试、安全检查
commit-msg 提交消息写入后 校验提交消息格式
pre-merge-commit 合并前 检查合并冲突
post-checkout git checkout 环境初始化
post-commit 提交完成后 通知、日志记录
post-merge 合并完成后 依赖更新检查
prepare-commit-msg 编辑提交消息前 自动生成提交消息模板

阶段确定机制:

Hook 运行在哪个阶段,按以下优先级确定:

  1. 单个 hook 的 stages 字段(最高优先级)
  2. 全局 default_stages(次优先级)
  3. 工具默认值(最低优先级)
1
2
3
4
5
6
7
8
9
10
11
12
default_stages: [pre-commit]    # 全局默认

repos:
- repo: https://github.com/psf/black
hooks:
- id: black
# 没指定 stages → 使用 default_stages → [pre-commit]

- repo: local
hooks:
- id: pytest
stages: [pre-push] # 显式指定 → 覆盖默认值

7.5 手动运行pre-commit

1
2
3
4
5
# 对所有文件运行
uv run pre-commit run --all-files

# 对暂存的文件运行(模拟commit时的行为)
uv run pre-commit run

pre-commit的工作流程:

方案A(Ruff):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
git add .                    # 暂存文件
git commit -m "xxx" # 触发pre-commit

[trailing-whitespace] 检查行尾空格

[end-of-file-fixer] 检查文件结尾

[ruff] 检查代码规范

[ruff-format] 检查格式

[mypy] 类型检查

全部通过 → 提交成功
任一失败 → 提交取消,显示错误信息

方案B(Black + isort + flake8):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
git add .                    # 暂存文件
git commit -m "xxx" # 触发pre-commit

[trailing-whitespace] 检查行尾空格

[end-of-file-fixer] 检查文件结尾

[black] 代码格式化

[isort] 导入排序

[flake8] 代码检查

[mypy] 类型检查

全部通过 → 提交成功
任一失败 → 提交取消,显示错误信息

八、GitHub Actions配置

8.1 什么是GitHub Actions?

GitHub Actions是GitHub提供的CI/CD服务。它可以在代码推送、PR创建等事件时自动运行测试和检查。

为什么需要CI/CD?

  • 双重保障:本地pre-commit可能被跳过(--no-verify),CI是最后的防线
  • 多环境测试:可以在多个Python版本、多个操作系统上测试
  • 自动化发布:可以自动发布到PyPI

8.2 创建GitHub Actions配置

根据你选择的工具方案,创建 .github/workflows/ci.yml


方案A:Ruff版CI配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# GitHub Actions CI配置(Ruff版)
name: CI

# 触发条件
on:
# 推送到main分支时触发
push:
branches: [main]
# Pull Request时触发
pull_request:
branches: [main]

# 作业定义
jobs:
# ============================================
# 代码质量检查
# ============================================
lint:
name: Lint
runs-on: ubuntu-latest
steps:
# 检出代码
- uses: actions/checkout@v4

# 安装uv
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

# 设置Python
- name: Set up Python
run: uv python install

# 安装依赖
- name: Install dependencies
run: uv sync --all-extras

# Ruff检查
- name: Ruff check
run: uv run ruff check .

# Ruff格式检查
- name: Ruff format check
run: uv run ruff format --check .

# MyPy类型检查
- name: MyPy
run: uv run mypy src/

# ============================================
# 测试
# ============================================
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
# 在lint完成后运行
needs: lint
# 矩阵策略:多Python版本测试
strategy:
matrix:
python-version: ["3.11", "3.12"]

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --all-extras

# 运行测试
- name: Run tests
run: uv run pytest --cov=src --cov-report=xml

# 上传覆盖率报告
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
files: coverage.xml
fail_ci_if_error: false

方案B:Black + isort + flake8版CI配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# GitHub Actions CI配置(传统工具链版)
name: CI

# 触发条件
on:
# 推送到main分支时触发
push:
branches: [main]
# Pull Request时触发
pull_request:
branches: [main]

# 作业定义
jobs:
# ============================================
# 代码质量检查
# ============================================
lint:
name: Lint
runs-on: ubuntu-latest
steps:
# 检出代码
- uses: actions/checkout@v4

# 安装uv
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

# 设置Python
- name: Set up Python
run: uv python install

# 安装依赖
- name: Install dependencies
run: uv sync --all-extras

# Black格式检查
- name: Black format check
run: uv run black --check .

# isort导入排序检查
- name: isort check
run: uv run isort --check-only .

# flake8代码检查
- name: flake8 check
run: uv run flake8 .

# MyPy类型检查
- name: MyPy
run: uv run mypy src/

# ============================================
# 测试
# ============================================
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
# 在lint完成后运行
needs: lint
# 矩阵策略:多Python版本测试
strategy:
matrix:
python-version: ["3.11", "3.12"]

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --all-extras

# 运行测试
- name: Run tests
run: uv run pytest --cov=src --cov-report=xml

# 上传覆盖率报告
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
files: coverage.xml
fail_ci_if_error: false

8.3 理解工作流配置

触发条件 (on):

1
2
3
4
5
on:
push:
branches: [main] # 推送到main分支时
pull_request:
branches: [main] # PR到main分支时

拓展:其他常用触发条件

1
2
3
4
5
6
on:
schedule:
- cron: '0 0 * * *' # 每天0点运行
workflow_dispatch: # 手动触发
release:
types: [published] # 发布时触发

作业依赖 (needs):

1
2
test:
needs: lint # test作业必须等待lint完成

矩阵策略 (strategy.matrix):

1
2
3
4
5
strategy:
matrix:
python-version: ["3.11", "3.12"]
os: [ubuntu-latest, windows-latest]
# 这会产生2x2=4个作业组合

九、完整工作流演示

现在让我们把所有内容串起来,完成一次完整的工作流。

9.1 首次提交并推送

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 确保所有文件都已保存

# 2. 查看当前状态
git status

# 3. 添加所有文件到暂存区
git add .

# 4. 提交(会触发pre-commit)
git commit -m "feat: 初始化项目结构和工具链"

# 如果pre-commit检查失败,修复后重新add和commit

9.2 关联远程仓库并推送

1
2
3
4
5
6
7
8
9
10
11
# 添加远程仓库(使用SSH方式)
git remote add origin git@github.com:你的用户名/python-demo.git

# 或者使用HTTPS方式
# git remote add origin https://github.com/你的用户名/python-demo.git

# 查看远程仓库
git remote -v

# 推送到远程仓库(会触发pre-push钩子运行测试)
git push -u origin main

9.3 查看CI结果

  1. 打开 https://github.com/你的用户名/python-demo
  2. 点击 Actions 标签
  3. 你应该能看到正在运行或已完成的工作流
  4. 点击工作流可以查看详细日志

9.4 日常开发流程

从现在开始,你的日常开发流程是:

方案A(Ruff):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. 编写代码
# ... 在VS Code中编辑文件 ...

# 2. 保存文件(VS Code会自动格式化)

# 3. 运行本地检查(可选,提交时会自动运行)
uv run ruff check .
uv run mypy src/
uv run pytest

# 4. 提交代码
git add .
git commit -m "feat: 添加新功能"
# pre-commit自动运行检查

# 5. 推送代码
git push
# pre-push自动运行测试
# GitHub Actions自动运行CI

方案B(传统工具链):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. 编写代码
# ... 在VS Code中编辑文件 ...

# 2. 保存文件(VS Code会自动格式化)

# 3. 运行本地检查(可选,提交时会自动运行)
uv run black --check .
uv run isort --check-only .
uv run flake8 .
uv run mypy src/
uv run pytest

# 4. 提交代码
git add .
git commit -m "feat: 添加新功能"
# pre-commit自动运行检查

# 5. 推送代码
git push
# pre-push自动运行测试
# GitHub Actions自动运行CI

9.5 处理pre-commit失败

如果pre-commit检查失败:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看失败原因,然后:

# 情况1:Ruff自动修复了问题
# 重新添加修改后的文件并提交
git add .
git commit -m "feat: 添加新功能"

# 情况2:需要手动修复
# 根据错误信息修改代码
# 然后重新 add 和 commit

# 紧急情况:跳过检查(不推荐)
git commit --no-verify -m "hotfix: 紧急修复"

十、总结与拓展

10.1 我们完成了什么

步骤 内容 为什么
Git仓库 GitHub + SSH 版本控制和协作
项目结构 src layout 专业的项目组织
依赖管理 uv + pyproject.toml 快速、现代的包管理
代码质量 Ruff 或 Black+isort+flake8 + MyPy 自动检查和修复
测试 pytest + coverage 保证代码正确性
IDE配置 VS Code 高效的开发体验
本地检查 pre-commit 提交前自动检查
CI/CD GitHub Actions 持续集成保障

10.2 工具链总览

方案A(Ruff):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
开发流程:
编写代码 → 保存(自动格式化)→ git add → git commit

pre-commit运行
├── trailing-whitespace
├── ruff check
├── ruff format
└── mypy

git push

pre-push运行
└── pytest

GitHub Actions
├── lint作业
└── test作业(多版本)

方案B(传统工具链):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
开发流程:
编写代码 → 保存(自动格式化)→ git add → git commit

pre-commit运行
├── trailing-whitespace
├── black
├── isort
├── flake8
└── mypy

git push

pre-push运行
└── pytest

GitHub Actions
├── lint作业
└── test作业(多版本)

10.3 进阶学习路线

你已经掌握了基础,接下来可以学习:

  1. 测试进阶

    • pytest fixtures
    • 参数化测试
    • Mock和打桩
    • 集成测试
  2. CI/CD进阶

    • 自动发布到PyPI
    • Docker构建
    • 多环境部署
  3. 代码质量进阶

    • 更多Ruff规则
    • 自定义MyPy插件
    • 安全检查(bandit)
  4. 文档

    • Sphinx文档生成
    • MkDocs
    • API文档

10.4 常见问题

Q: pre-commit太慢了怎么办?

A: 可以配置只检查变更的文件,或者在CI中运行完整检查,本地只运行快速检查。Ruff方案通常比传统工具链快很多。

Q: 如何在两种方案之间切换?

A: 按以下步骤操作:

  1. 修改 pyproject.toml 中的 [project.optional-dependencies] dev依赖
  2. 替换对应的工具配置节([tool.ruff][tool.black]/[tool.isort]
  3. 更新 .pre-commit-config.yaml 使用对应的hooks
  4. 更新 .vscode/settings.json.vscode/extensions.json
  5. 运行 uv sync --all-extras 重新安装依赖

Q: 如何在现有项目中应用这套工具?

A: 渐进式采用:

Ruff方案:

  1. 先添加配置文件
  2. 运行 ruff check --fix 修复简单问题
  3. 运行 ruff format 格式化代码
  4. 提交修复
  5. 启用pre-commit

传统工具链方案:

  1. 先添加配置文件
  2. 运行 black . 格式化代码
  3. 运行 isort . 排序导入
  4. 运行 flake8 . 检查问题并手动修复
  5. 提交修复
  6. 启用pre-commit

项目文件清单

完成本教程后,你的项目结构应该是:

方案A(Ruff):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
python-demo/
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions配置
├── .vscode/
│ ├── extensions.json # 推荐扩展
│ └── settings.json # 工作区设置
├── src/
│ └── python_demo/
│ ├── __init__.py # 包初始化
│ └── calculator.py # 计算器模块
├── tests/
│ ├── __init__.py
│ └── test_calculator.py # 测试文件
├── .gitignore # Git忽略配置
├── .pre-commit-config.yaml # pre-commit配置
├── .python-version # Python版本
├── pyproject.toml # 项目配置
├── README.md # 项目说明
└── uv.lock # 依赖锁文件

方案B(传统工具链):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
python-demo/
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions配置
├── .vscode/
│ ├── extensions.json # 推荐扩展
│ └── settings.json # 工作区设置
├── src/
│ └── python_demo/
│ ├── __init__.py # 包初始化
│ └── calculator.py # 计算器模块
├── tests/
│ ├── __init__.py
│ └── test_calculator.py # 测试文件
├── .flake8 # flake8配置(额外文件)
├── .gitignore # Git忽略配置
├── .pre-commit-config.yaml # pre-commit配置
├── .python-version # Python版本
├── pyproject.toml # 项目配置
├── README.md # 项目说明
└── uv.lock # 依赖锁文件

相关文章

祝你学习愉快!🎉