
本文讲解如何将 pre-commit 钩子(如 ruff、black、mypy)复用于 ci 流水线,确保本地开发与远程构建使用完全一致的检查工具和版本,兼顾开发效率与代码质量保障。
本文讲解如何将 pre-commit 钩子(如 ruff、black、mypy)复用于 ci 流水线,确保本地开发与远程构建使用完全一致的检查工具和版本,兼顾开发效率与代码质量保障。
在现代 Python 工程实践中,pre-commit 是提升代码规范性的利器;而 CI(如 GitHub Actions、GitLab CI 或 Travis CI)则是保障协作质量的最后一道防线。但二者若独立维护——例如 pre-commit 中配置了 ruff==0.4.0,CI 中却硬编码 pip install ruff==0.3.9——极易导致“本地通过、CI 失败”的尴尬局面,破坏一致性与可维护性。
核心原则:统一入口,一次定义,多处执行
不要把 linting 逻辑分散在 .pre-commit-config.yaml 和 .github/workflows/ci.yml 两个地方。推荐做法是:将所有检查封装为可复用的脚本或命令,并由 pre-commit 和 CI 共同调用该统一入口。
✅ 推荐实践:基于 poetry + poe 的标准化检查命令
假设你已使用 poetry 管理依赖,并通过 poethepoet 定义任务,可在 pyproject.toml 中声明标准化检查任务:
[tool.poe.tasks] lint = "ruff check . && ruff format --check . && mypy src/" format = "ruff format ." type-check = "mypy src/"
然后在 .pre-commit-config.yaml 中复用该命令(无需重复配置 Ruff 版本):
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-yaml
- repo: local
hooks:
- id: poetry-lint
name: Run poetry lint
entry: poe lint
language: system
types: [python]
CI 流水线(以 GitHub Actions 为例)同样调用同一命令,且自动继承 pyproject.toml 中声明的依赖版本:
# .github/workflows/ci.yml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: snok/install-poetry@v1
- run: poetry install
- run: poetry run poe lint
这样,当你升级 ruff 版本时,只需在 pyproject.toml 的 [tool.ruff] 或 poetry add ruff@latest 中统一更新,pre-commit 和 CI 将自动同步使用新版本,零配置遗漏风险。
⚠️ 注意事项:
- 避免在 CI 中直接写 pip install ruff && ruff check . —— 这会绕过项目依赖锁定,破坏版本一致性;
- pre-commit 的 language: system + entry: poe lint 依赖环境已安装 poe,CI 中需确保 poetry install 后 poe 可用(可通过 poetry run poe --help 验证);
- 若团队成员跳过 pre-commit(如 git commit --no-verify),CI 仍会强制拦截,这是设计所需,而非缺陷;
- 对于耗时较长的检查(如 deep type checking),可考虑 CI 分阶段运行:lint(快速)→ test → type-check(可选并行)。
总结:pre-commit 是开发者的“第一道加速反馈”,CI 是项目的“最终质量守门员”。二者不是替代关系,而是互补协同。通过将检查逻辑抽象为项目级可执行任务(如 poe lint),即可实现配置一处、生效全域,真正达成 “一次定义,处处一致” 的工程化目标。











