Ruff 替换 flake8 + black + isort:一个中型 Python 仓库的迁移实录
团队里一个 12 万行的 Python 仓库,lint 链路一直是 flake8 + black + isort 三件套串联。每次 pre-commit 要 4 秒多,CI 的 lint job 接近一分钟,而且三份配置分散在 setup.cfg、pyproject.toml 和 .isort.cfg 里,改一次规则要翻三个文件。上周我把它们整体换成了 Ruff,记录一下过程和踩过的坑,给同样在犹豫的团队一个参考。
一、为什么换
Ruff 是用 Rust 写的,一个二进制同时承担 lint 和 format,官方宣称比 flake8 快几十倍。对我们这种中型仓库来说,真正吸引人的不是跑分,而是一套配置、一条命令、一次遍历:不用再让三个工具各自 parse 一遍 AST,也不用担心 black 格式化完之后 isort 又把 import 组重排。
二、配置收敛到 pyproject.toml
装好之后,把原来的三份配置删掉,规则集中在 pyproject.toml:
[tool.ruff]
line-length = 100
target-version = "py311"
src = ["src", "tests"]
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM", "C4"]
ignore = ["E501"]
[tool.ruff.lint.isort]
known-first-party = ["mypkg"]
[tool.ruff.format]
quote-style = "double"
注意 known-first-party 一定要从原来的 isort 配置迁过来,否则 ruff 会把自家包当成第三方,import 分组直接乱掉,review 时一片红。
三、规则不要一次开全
我第一版直接把 select 写成 ["ALL"],结果报了两千多条。建议分三步走:先 E/F 对齐原来的 flake8,再开 I 把 import 排序接过来,最后再逐步打开 UP(pyupgrade)、B(bugbear)、SIM(简化)。每加一组之前先用 diff 看清楚影响面:
ruff check . --select I --diff | head -50
这样每一次提交都是一个可 review 的改动范围,而不是一次几千文件的格式化爆炸。
四、pre-commit 与 CI 落地
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
CI 里直接加一个 step 跑 ruff check . && ruff format --check . 即可。Ruff 是单二进制,不需要再装一圈插件包,镜像构建也快了。
五、踩过的坑
1. format 风格差异:ruff format 默认双引号、magic trailing comma 策略与 black 略有不同。先单独提交一次全量 format,不要和业务改动混在同一个 commit 里,否则 diff 无法 review。
2. noqa code 变了:原来 flake8 插件专属的 code(比如某些 plugin 的 W 系列)Ruff 没有等价实现,要么换用 Ruff 对应规则,要么临时改成裸 # noqa,别一直挂着。
3. 版本要钉死:Ruff 迭代很快,新版本可能新增规则导致流水线突然变红。pre-commit 的 rev 和 CI 的 pin 必须一致,并且统一升级节奏。
4. 编辑器双写:VS Code 装了 Ruff 扩展后,记得关掉 Python 扩展自带的 black / flake8 格式化,否则保存时两个 formatter 互相覆盖,保存一次文件抖动两次。
六、效果
本地 pre-commit 从 4.2 秒降到 0.35 秒,CI lint 阶段从 58 秒降到 6 秒左右;依赖从 black + flake8 + isort + 若干 flake8 插件共 11 个包收敛到 1 个。规则集反而更严格了——多了 bugbear 和 pyupgrade 的检查,还顺手抓到几处 except: pass 和可简化的 if/else。
结论:如果你还在维护 flake8 + black + isort 三件套,迁移成本大概是一个下午,收益是每天都在省的时间。
- 点赞
- 收藏
- 关注作者
评论(0)