Skip to content
BaiRuic's Blog
Go back

Ruff Ruff Ruff

Updated:

本文适用于Code Formatting爱好者和有一定经验的Pythonista。当然,如果初学者一开始便有clean code的意识且能做到,那真是太妙了。

引入

在软件开发中,编写干净、无错误的Python代码对于维护代码质量、可读性和可靠性至关重要。Python开发通常会使用Linter 和 Formatter来保证代码质量。

Linter

Linter 是代码检查工具,它可以在不运行代码的形况下(静态检查)检查代码中的语法错误,以及一些不规范的写法。类似JS中的ESLint。但是Python关于Linter并没有完全实现大一统,常见的Linter包括:

目前最为流行的是Flake8,可同时通过静态分析检查代码风格和错误。此外,通常还会配置一个类型检查的Linter,我个人倾向使用PyRight。这两个Linter在VS Code中均有插件可以直接安装。

Formatter

Formatter是代码格式化工具,Linter仅帮助我们发现代码中的风格问题,而Formatter可以将不符合规范的代码自动的格式化。常用的Formatter包括:

💡

也有人把isort归类到linter中,见仁见智罢了,本文不纠结这个。

此前,如果使用VS Code开发Python,需要分别配置 Linter 和 Formatter,主流的搭配是:Flake8 + isort + Black Formatter ,再加一个Pylance。但如今,Ruff出来后,Flake8 + isort + Black 便可以all-in-one了。

What Ruff?

Ruff是一个集 Python linter 和 formatter 为一体的工具。

Linter功能

Flake8 作为 Python 开发中最流行的 Linter,Ruff为了实现对已有项目的无感迁移,连错误代码都与Flake8几乎保存一致,这极大降低了从Flake8到Ruff的成本。

此外,Ruff还具备简单的 format 能力—— fixes**,**比如移除未使用的import、格式化文档字符串等。

这和 Flake8 只做单纯的静态分析不同,Ruff linter 在检查过程中,能够直接对代码进行修改。 当然,如果你不习惯,该功能是可以直接关闭的。

最后补充一点,Ruff对导入的包排序的功能(isort (I))在linter中,用 Ruff 的规则 I 实现,可以通过select = ["I"]配置开启。

Formatter功能

不同于Linter,不同的Formatter之间几乎完全无兼容性可言,每个Formatter都有自己的定义,例如相当流行的yapf和Black之间就相差很多,这一点不像 Linter 还可以多个叠加使用。

所以如果Ruff要开发Formatter功能,一定要和目前主流的某个Formatter高度兼容,这才能吸引用户使用它,否则你换了Ruff的 Formatter,却带来一堆格式化结果改动,绝对会造成迁移负担与采用意愿的下降。

考虑到 Black 在 Python 生态系统中的受欢迎程度,Ruff的Formatter最终选择了作为Black的替代。按照文档的描述,除了少许偏差之外,Ruff的Formatter和Black的兼容性高达99.9%。

👉

关于引号风格一直是Python中有争议的一个点,Black为此做了一定妥协,可由用户通过**skip-string-normalization**来自定义配置。 而Ruff除了可配置引号风格之外,还额外增加可调整的一些配置选项,如缩进风格等。

目前,Ruff的formatter还不支持对导入的包排序,但是该功能可以在linter中实现。

Why Ruff?

Black是Python开源社区公认的最流行的代码格式化工具,flake8也有着丰富的生态。为什么还要换Ruff呢?主要有以下几点原因:

VS Code设置

💡

除非需要在命令行运行 Ruff,否则没有必要在项目中安装 Ruff 包(比如 pip install ruff),因为 VS Code 插件自己带了一个独立的 Ruff 可执行文件,且是全局的。

像Linter、Formatter这类工具我是非常建议在VS Code中进行全局设定的,这样不用一个虚拟环境甚至一个项目配置一次,当然这仅限于个人开发项目。

Ruff也支持在项目中通过 pyproject.toml 配置,要是团队合作,把配置信息放到项目文件夹中的pyproject.toml会更合适,这样可以避免团队成员使用不一致的配置。

在VS Code中,安装Ruff插件后需要设置Python行为和notebook行为,如下:

{
	"[python]": {
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
        "source.fixAll": "explicit",
        "source.organizeImports": "explicit"
    },
    "editor.defaultFormatter": "charliermarsh.ruff"
	},
  "notebook.formatOnSave.enabled": true,
  "notebook.codeActionsOnSave": {
    "notebook.source.fixAll": "explicit",
    "notebook.source.organizeImports": "explicit"
  },
}

其中:

  1. **editor.formatOnSave**:保存时自动格式化(对所有 formatter 都有效)。
  2. **source.fixAll**:保存时自动 fix。 (类似 pre-commit 时,hook 的自动修正)
  3. **source.organizeImports**:保存时自动排序 imports。

然后再设置Ruff插件的配置信息,关于这一点,如果项目中已有专属的Ruff配置信息,那么此处是无需设置的。

以下是我VS Code中关于Ruff插件的配置。

"ruff.lint.args": [
    "--line-length=100",
],
"ruff.organizeImports": true,
"ruff.fixAll": true,
"ruff.showNotifications": "onError",

团队合作时,如果仅在自己的VS Code中全局配置Ruff插件,很容易导致团队成员之间的配置信息不一致。因此,此时更建议在项目文件中通过pyproject.tomlruff.toml.ruff.toml 文件来配置 Ruff插件。

💡

关于Ruff的配置,除了查阅官方文档,我更建议去看看(借鉴)那些已经采用了Ruff的开源项目的配置文件。

以下便是我借鉴fastapitransformers的Ruff插件配置:

[tool.ruff]
line-length = 120

[tool.ruff.lint]
select = [
    "E",  # pycodestyle errors
    "W",  # pycodestyle warnings
    "F",  # pyflakes
    "I",  # isort
    "C4",  # flake8-comprehensions
    "B",  # flake8-bugbear

ignore = [
    "E501",  # line too long
    "B008",  # do not perform function calls in argument defaults
    "C901",  # too complex
    "W191",  # indentation contains tabs
]

[tool.ruff.format]
# format code snippets in docstrings
docstring-code-format = true

# Like Black, use double quotes for strings.
quote-style = "double"

# Like Black, indent with spaces, rather than tabs.
indent-style = "space"

# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false

# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"

[tool.ruff.lint.isort]
lines-after-imports = 2
combine-as-imports = true

小遗憾

Ruff 明确表示不会提供类型检查的功能 ,因此,无法通过Ruff来帮助你找出潜在的类型匹配错误, 所以目前Pylance 插件依旧无法被取代。

参考


Share this post:

Previous Post
转载 Donoho 数据科学 50 年
Next Post
pickle反序列化时的ImportError