10.1 迁移之前:了解"映射关系"
Ruff 大部分规则都是逐条从 Flake8 生态复刻过来的,对照表能让你心里有底:
| 旧工具 | Ruff 对应族 | 说明 |
|---|---|---|
| pyflakes | F | 100% 覆盖 |
| pycodestyle | E/W | 100% 覆盖 |
| isort | I | 100% 覆盖 |
| pyupgrade | UP | 100% 覆盖 |
| autoflake | F401/F841 + --fix | 覆盖 |
| flake8-bugbear | B | 覆盖 |
| flake8-bandit | S | 大部分覆盖 |
| flake8-comprehensions | C4 | 覆盖 |
| flake8-simplify | SIM | 覆盖 |
| pydocstyle | D | 覆盖 |
| pep8-naming | N | 覆盖 |
| Black | ruff format | 99% 兼容 |
| pylint | PL(子集) | 部分覆盖;深度类型推断不覆盖 |
| mypy / pyright | 不覆盖 | 保留作为独立工具 |
10.2 七步迁移清单
① 装 Ruff,保留旧工具
$ uv add --dev ruff # 旧 flake8/black/isort 先不删
② 翻译旧配置
有官方工具一键转换(仅支持 Flake8 的 setup.cfg / .flake8):
$ ruff check --generate-shell-completion bash # 非迁移
# 等价的转换通常手写,参照下面的对照
常见旧 → 新对照:
# 旧:.flake8
[flake8]
max-line-length = 100
extend-ignore = E203, W503
per-file-ignores = __init__.py:F401
# 新:pyproject.toml
[tool.ruff]
line-length = 100
[tool.ruff.lint]
select = ["E", "F"]
ignore = ["E203"] # W503 在 Ruff 中不存在,Black 风格天然兼容
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
③ 格式化:ruff format 一次性对齐
$ ruff format .
$ git commit -am "style: switch formatter to ruff format"
从 Black 迁来时这个 diff 通常很小(< 1%),单独 commit 便于 review。
④ 先开"零风险"的规则族
[tool.ruff.lint]
select = ["E", "F", "W", "I"] # Flake8 等价集合
$ ruff check --fix .
$ git commit -am "style(ruff): baseline equivalent to flake8"
到这一步,Ruff 已经"做了原来 Flake8 做的事"。
⑤ 逐步扩大规则集合
每次 PR 只新增一族。例如:
# PR #1: 升级语法
extend-select = ["UP"]
$ ruff check --select UP --fix .
# PR #2: bugbear 抓反模式
extend-select = ["UP", "B"]
$ ruff check --select B --fix --unsafe-fixes . # 人工 review
# PR #3: 简化代码
extend-select = [..., "SIM", "C4"]
⑥ 删除旧工具
$ uv remove --dev flake8 black isort autoflake pyupgrade
# 同时清掉 .flake8 / setup.cfg 里相应配置
⑦ CI & pre-commit 切换
把 CI 里的 flake8 . / black --check . 替换成 ruff check . + ruff format --check .,pre-commit 改 ruff-pre-commit(见第 8 章)。
10.3 "大仓一次性启用"的秘密武器
千万行级仓库一次性切 Ruff,常常冒出几万条违规。除了分批启用规则外,还有两招:
--add-noqa:给所有违规现行加 noqa
$ ruff check --add-noqa .
# CI 立刻变绿;之后每次 PR 清理几条 noqa,技术债可见
Baseline 模式(第三方工具)
Ruff 目前还没内置 baseline,但社区工具 ruff-baseline 可在迁移时冻结"当前所有错误"为已知清单,新代码才触发 CI 失败。
10.4 和 mypy / pyright 协同
Ruff 不做类型检查,所以和 mypy 并用是常态:
# CI 两条独立 job,并行运行
jobs:
ruff:
steps:
- uses: astral-sh/ruff-action@v3
mypy:
steps:
- uses: actions/setup-python@v5
- run: pip install mypy
- run: mypy src
Astral ty:未来的替代者
Astral 正在开发 ty(Rust 写的类型检查器),目标是未来取代 mypy。它目前仍在预览期(2026 年的发布计划中),但思路和 Ruff 一致——速度是关键。目前生产环境仍推荐 mypy/pyright/basedpyright。
10.5 常见差异与坑
- W503 / W504(line break before binary operator)
- Ruff 不实现这两条——Black 早在 2019 年就确立"换行应在运算符前",两者一致。
- Black 的 "preview" 特性
- Black 有
--preview实验特性,ruff format的目标是对齐 Black stable(非 preview)。所以 Black 启用 preview 时两者会有差异。 - E203(whitespace before ':')
- 该规则与 Black/Ruff format 的切片写法冲突。惯例是
ignore = ["E203"]。 - pylint 的跨文件类型推断
- Ruff 的 PL 族只实现了 pylint 的一部分,深度类型推断(未调用函数签名不匹配等)仍需 pylint 或 mypy。
10.6 终极推荐配置(2026 年版)
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
select = [
"E", "W", "F", # Flake8 核心
"I", # isort
"N", # 命名
"UP", # 升级语法
"B", # bugbear
"C4", # 推导式
"SIM", # simplify
"PTH", # pathlib
"RUF", # Ruff 原创
"S", # 安全(bandit)
]
ignore = ["E501", "S101"]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
"tests/**/*.py" = ["S101", "ANN", "D"]
"**/migrations/*.py" = ["E501", "N806"]
[tool.ruff.format]
quote-style = "double"
docstring-code-format = true
[tool.ruff.lint.isort]
known-first-party = ["myapp"]
10.7 学习路径 & 参考
- 官方文档:docs.astral.sh/ruff——规则字典非常详尽,遇到 code 点进去有 Good/Bad 对比。
- Rules 列表:docs.astral.sh/ruff/rules
- 配置参考:docs.astral.sh/ruff/settings
- ruff-pre-commit:github.com/astral-sh/ruff-pre-commit
- ruff-action:github.com/astral-sh/ruff-action
10.8 全书总结
- Ruff = Flake8 + Black + isort + pyupgrade + bandit 等 10+ 工具的 Rust 重实现,快 10-100 倍。
- 核心命令两条:
ruff check(找问题/修复)+ruff format(格式化)。 - 配置集中在
pyproject.toml的[tool.ruff]段,团队共享。 - 本地靠 LSP/pre-commit,CI 靠
ruff-action,分层防御。 - 迁移分步走:先等价替换 → 分族扩展 → 删旧工具 → 与 mypy 协同。
至此 10 章内容完成。Ruff 正在重塑 Python 工具链——越早迁越多受益。祝你写代码更清爽、CI 更快、评审更少扯皮。