Ruff FAQ 深度解读:作为 Black、Flake8、Pylint 与 isort 的替代方案,如何从零迁移配置
【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff
Ruff 是一个用 Rust 编写、以速度见长的 Python linter 与代码格式化器。本文以仓库中的 docs/faq.md 为骨架,系统梳理 Ruff 与 Black、Flake8、Pylint、isort、Mypy 等工具的兼容性与差异,完整展开 FAQ 中关于安装、配置、src解析、docstring 约定、preview 规则、safe/unsafe 修复等实战细节,并结合 crates/ruff_workspace/src/options.rs 等源码印证底层实现。读完后你将能判断 Ruff 是否适合替换现有工具链,并完成一份可落地的迁移配置。
Ruff 与 Black 的关系:linter 兼容性与 formatter 差异
linter 与 Black 开箱即用兼容
FAQ 明确指出:只要line-length设置一致,Ruff 的 linter 与 Black 开箱即用兼容。Ruff 的设计哲学是与格式化器(Ruff 自身的 formatter 或 Black)配合使用,因此会刻意不实现那些已被自动格式化覆盖的纯风格类规则,避免 linter 与格式化器互相打架。
需要特别留意的是,Ruff 的 linter 与 Black 对行长约束的理解并不完全一致:
- Black(以及 Ruff 的 formatter)会尽力遵守
line-length,但在某些场景(如注释内部)会刻意避免自动换行; - Ruff 的 linter 则会对任何超过
line-length的行直接报line-too-long(E501)。
因此在启用了E501的前提下,即使代码已经通过 Black 或ruff format格式化,Ruff 仍可能触发行长违规。这一点在源码中也有印证:E501由 crates/ruff_linter/src/rules/pycodestyle/rules/line_too_long.rs 实现,其测试用例(crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__line-too-long_E501.py.snap)中可以看到E501 Line too long (123 > 88)这样的判定输出。
关于line-length的底层行为,crates/ruff_workspace/src/options.rs 中的文档注释补充了细节:长度按每行字符数计算,但包含东亚字符或 emoji 的行会按 Unicode 宽度 单独为E501配置与 formatter 不同的行长。
formatter 与 Black 的对比:drop-in replacement
Ruff 的 formatter 被设计为 Black 的drop-in replacement(即插即用替代品)。具体来说:
- 对已经按 Black 风格格式化的代码,formatter 输出与其近乎一致;FAQ 给出的实测口径是,在 Django、Zulip 等大规模 Black 格式化项目上,超过 99.9% 的行格式化结果完全相同;
- 迁移既有项目时,可以预期只有少量边缘差异;
- 对未按 Black 风格格式化的代码,Ruff 会做出一些不同于 Black 的决策,偏差会更多,尤其在**行尾注释(end-of-line comments)**的处理上。
更完整的风格决策说明见 docs/formatter.md(即 FAQ 引用的Style Guide章节)。
Ruff 与 Flake8:规则覆盖与插件重实现
适用前提与规则覆盖范围
FAQ 给出的结论是:Ruff 可以在以下条件下作为 Flake8 的 drop-in replacement:
- 不使用插件或只使用少量插件;
- 与 Black 搭配使用;
- 面向 Python 3 代码。
在上述条件下,Ruff 实现了 Flake8 的每一条规则。具体而言,Ruff 完整实现源自 Pyflakes 的全部F规则,以及源自 pycodestyle 的E、W规则子集。
原生重实现的流行 Flake8 插件
Ruff 以原生(Rust 实现)方式重实现了一批最流行的 Flake8 插件及周边代码质量工具,FAQ 完整列出的清单包括(括号内为对应规则或备注):
- autoflake、eradicate、yesqa
- flake8-2020、flake8-annotations、flake8-async、flake8-bandit、flake8-blind-except、flake8-boolean-trap、flake8-bugbear、flake8-builtins、flake8-commas、flake8-comprehensions、flake8-copyright、flake8-datetimez、flake8-debugger、flake8-django、flake8-docstrings、flake8-eradicate、flake8-errmsg、flake8-executable、flake8-gettext、flake8-implicit-str-concat、flake8-import-conventions、flake8-logging、flake8-logging-format、flake8-no-pep420、flake8-pie、flake8-print、flake8-pyi、flake8-pytest-style、flake8-quotes、flake8-raise、flake8-return、flake8-self、flake8-simplify、flake8-slots、flake8-super、flake8-tidy-imports、flake8-todos、flake8-type-checking、flake8-use-pathlib
- flynt、isort、mccabe、pandas-vet、pep8-naming、perflint、pydocstyle、pygrep-hooks、pyupgrade、tryceratops
这些工具的规则已被融入 Ruff 的规则注册表,规则实际代码分布在 crates/ruff_linter/src/rules/ 下按来源划分的各子目录(如pyflakes/、pycodestyle/、flake8_bugbear/、pydocstyle/、isort/等),并以F、E、W、B、D、I等前缀归组。
规则编码差异:为什么 Ruff 使用 TID252 而不是 I252
一个值得注意的细节是:部分规则虽然源自 Flake8 插件,但 Ruff 使用了不同的规则码与前缀。例如 flake8-tidy-imports 的I252在 Ruff 中对应TID252。这样设计有两个好处:
- 减少跨插件之间的规则码冲突;
- 允许通过
--select TID一条命令整体开关某个插件,而--select I2会与 isort 的规则(如I001)产生冲突。
相对 Flake8 的主要局限
FAQ 明确指出 Ruff 相对 Flake8 的核心局限是:不支持自定义 lint 规则(替代方案是热门的 Flake8 插件被以 Rust 原生重实现进 Ruff 本身)。另外,Ruff 并未包含 flake8-bugbear 中所有“有主见(opinionated)”的规则。
Ruff 与 Pylint:规则数量与能力边界
FAQ 给出的对比口径是:撰写 FAQ 时 Pylint 约有 409 条规则,而 Ruff 实现了 900+ 条规则,其中至少 209 条与 Pylint 规则集重叠(跟踪 issue 见 FAQ 引用的 #970)。需要注意这不是对称关系:
- Pylint 实现了许多 Ruff 没有的规则,反之亦然。例如 Pylint 能做更多类型推断(能校验函数调用的实参数目是否正确),这是 Ruff 目前不做的;
- 因此 Ruff 并不是 Pylint 的"纯粹" drop-in replacement,两者执行的规则集不同。
尽管如此,大量用户已成功从 Pylint 切换到 Ruff,尤其是将 Ruff 与类型检查器搭配使用的用户——类型检查器可以覆盖 Pylint 的部分能力(详见下文"Ruff 与类型检查器"一节)。
其他关键差异:
- 两者都支持插件(Pylint 称之为 checkers),但 Ruff 的所有规则均为原生实现,不支持自定义或第三方规则;
- 与 Pylint 不同,Ruff 能够自动修复自己的 lint 违规;
- 同名规则的结果可能有细微差异。FAQ 举例:Ruff 的
too-many-branches不像 Pylint 的R0912那样把try块单独计为分支——该规则在源码中对应 crates/ruff_linter/src/rules/pylint/rules/too_many_branches.rs; - Ruff 的
PL规则组还包含少量来自 Pylint扩展的规则,例如magic-value-comparison(对应 crates/ruff_linter/src/rules/pylint/rules/magic_value_comparison.rs),这些扩展规则在 Pylint 中需要显式激活;因此启用 Ruff 的PL组后,你可能会看到此前 Pylint 配置中从未启用过的违规。
Ruff 与类型检查器(Mypy / Pyright / Pyre):互补而非替代
Ruff 是linter 而非类型检查器。FAQ 用两个例子说明两者如何互补:
- Ruff 会通过扫描源码中对 import 的引用来提示"import 未使用",这是类型检查器通常不做的;
- 类型检查器能捕获"给期望字符串参数的函数传入了整数"这类类型错误,而 Ruff 会漏掉。
因此官方建议是:Ruff 与 Mypy / Pyright / Pyre 搭配使用——Ruff 提供更快的 lint 反馈,类型检查器提供更详细的类型错误反馈。这也呼应了 FAQ 在 Pylint 一节给出的迁移建议。
Ruff 到底能替代哪些工具:一张替换清单
综合 FAQ,Ruff 目前可以替代的工具可以归纳为两组:
- Flake8 及其插件(完整列表见上文,其中与第 2 节重复的插件包括 flake8-2020、flake8-annotations、flake8-async、flake8-bandit、flake8-blind-except、flake8-boolean-trap、flake8-bugbear、flake8-builtins、flake8-commas、flake8-comprehensions、flake8-copyright、flake8-datetimez、flake8-debugger、flake8-django、flake8-docstrings、flake8-eradicate、flake8-errmsg、flake8-executable、flake8-gettext、flake8-implicit-str-concat、flake8-import-conventions、flake8-logging、flake8-logging-format、flake8-no-pep420、flake8-pie、flake8-print、flake8-pytest-style、flake8-quotes、flake8-raise、flake8-return、flake8-self、flake8-simplify、flake8-slots、flake8-super、flake8-tidy-imports、flake8-todos、flake8-type-checking、flake8-use-pathlib、flynt、mccabe、pandas-vet、pep8-naming、perflint、pydocstyle、tryceratops);
- 独立工具:Black(格式化)、isort(import 排序)、yesqa(删除多余 noqa)、eradicate(删除注释掉的代码)、以及 pyupgrade 中实现的大部分规则。
如果你依赖某个未被支持的 Flake8 插件,FAQ 建议到项目 issue 区提交需求。
linter 与 formatter 是否必须绑定使用?
不必。FAQ 明确回答:Ruff 的 linter 和 formatter可以独立使用——可以只用 formatter 不用 linter,也可以反之。这为渐进式迁移提供了很大灵活性(例如先只引入格式化,再逐步开启 lint 规则)。
Python 版本支持与安装方式
支持的 Python 版本
- Ruff 可以为Python 3.7 及以上(包括 3.13)的代码做 lint;
- 不支持 Python 2;在 pre-3.7 代码上运行"可能"可行,但不属于官方支持范围(例如 Ruff 不解析类型注释 type comments);
- Ruff 本身可在 Python 3.7 及以上版本的环境下安装。
是否需要安装 Rust?
不需要。Ruff 以预编译产物形式发布在 PyPI(包名ruff),官方推荐用 uv 安装,也支持 pip、pipx 及多种包管理器(详见 docs/installation.md):
# 全局安装 Ruff。 $ uv tool install ruff@latest # 或者把 Ruff 加入项目依赖。 $ uv add --dev ruff # 用 pip。 $ pip install ruff # 用 pipx。 $ pipx install ruff从0.5.0起,还提供独立安装脚本:
# macOS 与 Linux。 $ curl -LsSf https://astral.sh/ruff/install.sh | sh # Windows。 $ powershell -c "irm https://astral.sh/ruff/install.ps1 | iex" # 指定版本(以 0.5.0 为例)。 $ curl -LsSf https://astral.sh/ruff/0.5.0/install.sh | sh $ powershell -c "irm https://astral.sh/ruff/0.5.0/install.ps1 | iex"Ruff 为所有主流平台提供 wheel 包,因此 uv、pip 等工具安装时完全不依赖 Rust 工具链。
能否为 Ruff 编写自定义 linter 插件?
目前尚不支持第三方插件,但插件系统在项目规划范围内(跟踪 issue 见 FAQ 引用的 #283)。在此之前,扩展 Ruff 能力的方式是:在 crates/ruff_linter/src/rules/ 中按既有模式原生实现规则,或贡献新规则到上游。
Ruff 与 isort:import 排序的兼容性与差异
Ruff 的 import 排序目标是:在使用 isort 的profile = "black"时与之近乎等价。已知差异包括:
- 别名 import 的分组方式不同。Ruff 倾向于把同一模块的非别名 import 聚在一起:
from numpy import cos, int8, int16, int32, int64, tan, uint8, uint16, uint32, uint64 from numpy import sin as np_sin而 isort 会在每个别名边界处拆成独立 import 语句:
from numpy import cos, int8, int16, int32, int64 from numpy import sin as np_sin from numpy import tan, uint8, uint16, uint32, uint64- 标准库识别更准确。Ruff 能正确把
_string、idlelib等 isort 无法识别的模块归类为标准库。 - 行内注释处理存在个别差异(详见 FAQ 引用的 #1381、#2104)。
与 Black 的兼容性方面,Ruff 的 import 排序与 isort 一样兼容 Black。Ruff 尚未支持 isort 的全部配置项,支持列表见 FAQ 引用的 settings API 参考(lint.isort配置段),例如下文的known-first-party/known-third-party就属于已支持项。
深度解读:Ruff 如何判定 first-party / third-party import
这一节是 FAQ 中实操性最强的部分之一,核心是src选项。
src 选项的作用
src配置项在pyproject.toml、ruff.toml或.ruff.toml中指定:Ruff 在判断某个 import 是否为 first-party 时应考察哪些目录。源码层面的说明见 crates/ruff_workspace/src/options.rs:该选项默认值为[".", "src"],支持 glob(如src = ["python_modules/*"]),并且会展开用户主目录和环境变量。
以一个典型项目结构为例:
my_project ├── pyproject.toml └── src └── foo ├── __init__.py └── bar ├── __init__.py └── baz.py判定算法如下:
- 当 Ruff 遇到
import foo时,会遍历src目录,寻找名为foo的目录或foo.py文件; - 对多级路径如
import foo.bar,要求相对路径foo/bar作为目录存在,或foo/bar.py/foo/bar.pyi作为文件存在; - 对
from foo import bar形式,只使用foo来判定 first-party / third-party。
误判规避:known-third-party
如果存在一个与第三方包同名、但不含 Python 代码的目录,上述算法可能把第三方 import 误判为 first-party。规避方式是通过known-third-party显式声明。例如项目src下存在与wandb同名的子目录:
# pyproject.toml [tool.ruff.lint.isort] known-third-party = ["wandb"]# ruff.toml [lint.isort] known-third-party = ["wandb"]默认行为与显式配置
- 若省略
src,Ruff 默认使用"project root"与"src"子目录作为 first-party 来源,同时兼容扁平与嵌套两种项目布局; - "project root"通常指包含
pyproject.toml、ruff.toml或.ruff.toml的目录;如果通过命令行--config显式指定配置文件,则以当前工作目录作为 project root; - 可将
src显式配置为唯一 first-party 来源:
# pyproject.toml [tool.ruff] # Ruff 用顶层 `src` 选项替代 isort 的 `src_paths` 设置。 # 所有路径都相对于 project root,即包含 pyproject.toml 的目录。 src = ["src"]# ruff.toml # Ruff 用顶层 `src` 选项替代 isort 的 `src_paths` 设置。 # 所有路径都相对于 project root,即包含 pyproject.toml 的目录。 src = ["src"]extends 与 project root 的关系
当配置文件通过extends继承另一个配置文件时,project root 仍然是当前配置文件所在目录(而非extends指向文件的目录)。例如在tests目录下添加配置文件并继承根配置时,需要显式调整src:
# pyproject.toml(位于 tests 目录) [tool.ruff] extend = "../pyproject.toml" src = ["../src"]# ruff.toml(位于 tests 目录) extend = "../pyproject.toml" src = ["../src"]同包启发式与 known-first-party
除了基于src的判定,Ruff 还会尝试确定某个 Python 文件所属的当前包(通过目录中是否存在__init__.py判断),并将同包内的 import 标记为 first-party。例如上述结构里baz.py属于从./my_project/src/foo开始的包,因此baz.py中以foo开头的 import(如import foo.bar)会基于该启发式被判为 first-party。src解析的详细说明见 FAQ 引用的 CONTRIBUTING 指南。
此外,还可以让某些模块无论位于文件系统何处都被视为 first-party,通过known-first-party实现。综合配置示例:
# pyproject.toml [tool.ruff] src = ["src", "tests"] [tool.ruff.lint] select = [ # Pyflakes "F", # Pycodestyle "E", "W", # isort "I001" ] [tool.ruff.lint.isort] known-first-party = ["my_module1", "my_module2"]# ruff.toml src = ["src", "tests"] [lint] select = [ # Pyflakes "F", # Pycodestyle "E", "W", # isort "I001" ] [lint.isort] known-first-party = ["my_module1", "my_module2"]Jupyter Notebook 支持与编辑器行为
原生支持与 nbQA 集成
Ruff 内置了对 Jupyter Notebook 的 lint 与格式化支持,详见 docs/configuration.md 中的 Jupyter Notebook 发现章节。同时 Ruff 还与 nbQA 集成——nbQA 是用于对 Notebook 运行 linter 与格式化器的工具。安装ruff与nbqa后即可对 notebook 运行:
$ nbqa ruff Untitled.ipynb Untitled.ipynb:cell_1:2:5: F841 Local variable `x` is assigned to but never used Untitled.ipynb:cell_2:1:1: E402 Module level import not at top of file Untitled.ipynb:cell_2:1:8: F401 `os` imported but unused Found 3 errors. 1 potentially fixable with the `--fix` option.注意诊断信息的定位格式是Untitled.ipynb:cell_1:2:5,即"文件名 + cell 序号 + 行列",便于在 Notebook 语境下定位问题。
Notebook 中 source.* 代码动作的坑
FAQ 特别提醒:Ruff不支持Jupyter Notebook 中的source.organizeImports与source.fixAll代码动作(VS Code 里的notebook.codeActionsOnSave),应改用notebook.source.organizeImports与notebook.source.fixAll。
原因在于 Ruff 需要看到 notebook 的完整内容才能给出准确诊断与修复——例如一个 cell 导入模块、另一个 cell 使用该模块,Ruff 必须同时看到两个 cell 才能把 import 判定为"已使用"。而source.*代码动作会要求并行地对每个 cell 单独执行修复,导致客户端对同一 notebook 重复应用相同修改,产生意外行为(FAQ 引用了 ruff-vscode 的 #680、#640、#391 三个 issue)。
支持 NumPy / Google 风格 docstring:convention 配置
Ruff 支持强制 docstring 约定,通过convention配置项实现,可选值为"google"、"numpy"或"pep257":
# pyproject.toml [tool.ruff.lint.pydocstyle] convention = "google" # 可选:"google"、"numpy" 或 "pep257"。# ruff.toml [lint.pydocstyle] convention = "google" # 可选:"google"、"numpy" 或 "pep257"。例如从 flake8-docstrings 迁移、原配置为--docstring-convention=numpy时,就按上述方式设置convention = "numpy"。
关键点:由于D规则默认未启用,设置convention的同时要显式开启D前缀:
# pyproject.toml [tool.ruff.lint] select = ["D"] [tool.ruff.lint.pydocstyle] convention = "google"# ruff.toml [lint] select = ["D"] [lint.pydocstyle] convention = "google"convention 的语义:启用某个 convention 会禁用所有不在该约定内的规则。因此推荐工作流是:先启用 convention,再在其基础上选择性开启或关闭个别规则:
# pyproject.toml [tool.ruff.lint] select = [ "D", # 在约定基础上增强:要求所有 docstring 使用祈使语气。 "D401", ] ignore = [ # 在约定基础上放宽:不要求为每个函数参数都写文档。 "D417", ] [tool.ruff.lint.pydocstyle] convention = "google"# ruff.toml [lint] select = [ "D", # 在约定基础上增强:要求所有 docstring 使用祈使语气。 "D401", ] ignore = [ # 在约定基础上放宽:不要求为每个函数参数都写文档。 "D417", ] [lint.pydocstyle] convention = "google"这些规则在源码层的文档注释也有对应体现:D401(祈使语气,对应 non-imperative-mood)与D417(参数文档缺失,对应 undocumented-param)在 crates/ruff_workspace/src/options.rs 的 convention 配置注释中被用作"增删规则"的示例。默认情况下不设置任何 convention,启用哪些规则完全由select决定。
"preview" 是什么?
preview 用于启用一批被视为实验性或不稳定的新规则与修复。相关说明见 docs/preview.md;当前处于 preview 状态的规则可查阅 规则参考(FAQ 引用的 rules reference)。
配置排查与调试手段
查看 Ruff 实际使用的设置
运行以下命令可查看某个文件最终解析出的全部设置:
$ ruff check /path/to/code.py --show-settings这在排查"为什么这条规则没生效 / 为什么行为与预期不符"时非常有用。
不用 pyproject.toml 可以吗?
可以,用ruff.toml即可。两者功能等价、schema 相同,唯一区别是ruff.toml可以省略[tool.ruff]段头:
# pyproject.toml [tool.ruff] line-length = 88 [tool.ruff.lint.pydocstyle] convention = "google"# ruff.toml line-length = 88 [lint.pydocstyle] convention = "google"注意 Ruff不支持INI 文件(如setup.cfg、tox.ini)。
修改默认配置:用户级配置文件
当找不到任何配置文件时,Ruff 会最后兜底查找用户级ruff.toml(行为类似 Flake8 的~/.config/flake8):
- macOS / Linux:
~/.config/ruff/ruff.toml,遵循XDG_CONFIG_HOME规范; - Windows:
~\AppData\Roaming\ruff\ruff.toml; - 历史说明:
v0.5.0之前,macOS 上从~/Library/Application Support/ruff/ruff.toml读取用户配置;该位置仍会被尊重,但已视为弃用。
该行为底层依赖etceteracrate(FAQ 原文引用),用于跨平台解析用户配置目录。
关于自动修复:safe 与 unsafe 修复机制
如果 Ruff 的修复破坏了代码,FAQ 给出了机制解释:Ruff 将修复标记为safe(安全)与unsafe(不安全)两类:
- 默认情况下,Ruff 只应用有 safe 修复的违规;
- unsafe 修复需要通过
unsafe-fixes设置(unsafe-fixes)或给ruff check传--unsafe-fixes标志启用。
在 crates/ruff_workspace/src/options.rs 中可以看到相关配置项:unsafe-fixes(默认仅自动应用安全修复)以及extend-unsafe-fixes(可把指定规则也纳入无需--unsafe-fixes即可应用的修复范围,例如extend-unsafe-fixes = ["E", "F401"])。更完整的说明见 docs/linter.md。
即便如此,鉴于 Python 的动态性,即使看似微不足道的修复也难以做到百分之百确定;若 safe 修复仍破坏了你的代码,FAQ 建议提交 issue。
输出颜色控制
Ruff 的颜色输出由coloredcrate 驱动,默认自动探测输出流是否支持颜色。可通过环境变量强制控制:
- 强制关闭:将
NO_COLOR设为任意值(如NO_COLOR=1); - 强制开启:将
FORCE_COLOR设为任意非空值(如FORCE_COLOR=1); - 同时兼容
CLICOLOR与CLICOLOR_FORCE两个环境变量(遵循 clicolors 规范)。
结语
通过 FAQ 全篇可以得出一个清晰的定位结论:Ruff 是一个"lint + format + import 排序 + 自动修复"一体化工具,与 Black、Flake8(含主流插件)、isort、pydocstyle、pyupgrade 等高度兼容甚至可直接替代,与 Pylint 存在规则集差异、需与类型检查器互补使用。迁移时的实操要点可以浓缩为四条:保持line-length一致、显式开启D/E等默认关闭的规则前缀、用src/known-first-party/known-third-party精确控制 import 判定、以及区分 safe/unsafe 修复的启用方式。结合 docs/configuration.md、docs/linter.md、docs/formatter.md 与 crates/ruff_workspace/src/options.rs,你可以在不引入任何额外插件的前提下,配置出一套足以覆盖原有 Flake8 + Black + isort 工具链的开发流程。
【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考