Chapter 10

从 Flake8 / Black / isort 迁移

一份可操作的迁移清单——从 pip 卸载旧工具到 CI 完全切换,期间分批启用规则、与 mypy 协同,全流程少踩坑。

10.1 迁移之前:了解"映射关系"

Ruff 大部分规则都是逐条从 Flake8 生态复刻过来的,对照表能让你心里有底:

旧工具Ruff 对应族说明
pyflakesF100% 覆盖
pycodestyleE/W100% 覆盖
isortI100% 覆盖
pyupgradeUP100% 覆盖
autoflakeF401/F841 + --fix覆盖
flake8-bugbearB覆盖
flake8-banditS大部分覆盖
flake8-comprehensionsC4覆盖
flake8-simplifySIM覆盖
pydocstyleD覆盖
pep8-namingN覆盖
Blackruff format99% 兼容
pylintPL(子集)部分覆盖;深度类型推断不覆盖
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 学习路径 & 参考

10.8 全书总结

至此 10 章内容完成。Ruff 正在重塑 Python 工具链——越早迁越多受益。祝你写代码更清爽、CI 更快、评审更少扯皮。