跳到内容

Ruff Linter

Ruff Linter 是一款速度极快的 Python 代码检查工具,旨在作为 Flake8(以及数十个插件)、isortpydocstylepyupgradeautoflake 等工具的直接替代品。

ruff check

ruff check 是 Ruff linter 的主要入口。它接受文件或目录列表,并对所有发现的 Python 文件进行代码检查,可选择自动修复所有可修复的错误。当检查目录时,Ruff 会递归地搜索该目录及其所有子目录中的 Python 文件。

$ ruff check                  # Lint files in the current directory.
$ ruff check --fix            # Lint files in the current directory and fix any fixable errors.
$ ruff check --watch          # Lint files in the current directory and re-lint on change.
$ ruff check path/to/code/    # Lint files in `path/to/code`.

有关支持选项的完整列表,请运行 ruff check --help

规则选择

启用的规则集可通过 lint.selectlint.extend-selectlint.ignore 设置进行控制。

Ruff 的 linter 沿用了 Flake8 的规则代码系统,每个规则代码由一到三个字母的前缀和三位数字组成(例如 F401)。前缀表示规则的“来源”(例如 F 代表 Pyflakes,E 代表 pycodestyle,ANN 代表 flake8-annotations)。

规则选择器(如 lint.selectlint.ignore)既接受完整的规则代码(例如 F401),也接受任何有效的规则前缀(例如 F)。例如,给定以下配置文件:

[tool.ruff.lint]
select = ["E", "F"]
ignore = ["F401"]
[lint]
select = ["E", "F"]
ignore = ["F401"]

Ruff 将启用所有以 E (pycodestyle) 或 F (Pyflakes) 为前缀的规则,但 F401 除外。有关通过 pyproject.toml 配置 Ruff 的更多信息,请参阅 配置 Ruff

作为特例,Ruff 还支持 ALL 代码,它会启用所有规则。请注意,某些 pydocstyle 规则存在冲突(例如 D203D211),因为它们代表了不同的文档字符串格式。当启用 ALL 时,Ruff 会自动禁用任何冲突规则。

如果您想知道如何配置 Ruff,以下是一些推荐准则

  • 优先使用 lint.select 而非 lint.extend-select,以使您的规则集更加明确。
  • 谨慎使用 ALL。启用 ALL 意味着每当您升级 Ruff 时,新规则都会被隐式启用。
  • 从一小组规则(select = ["E", "F"])开始,然后逐类添加。例如,您可以考虑扩展到 select = ["E", "F", "B"] 以启用流行的 flake8-bugbear 扩展。

例如,一个启用了一些最流行规则(且不太刻板)的配置可能如下所示:

[tool.ruff.lint]
select = [
    # pycodestyle
    "E",
    # Pyflakes
    "F",
    # pyupgrade
    "UP",
    # flake8-bugbear
    "B",
    # flake8-simplify
    "SIM",
    # isort
    "I",
]
[lint]
select = [
    # pycodestyle
    "E",
    # Pyflakes
    "F",
    # pyupgrade
    "UP",
    # flake8-bugbear
    "B",
    # flake8-simplify
    "SIM",
    # isort
    "I",
]

为了确定最终启用的规则集,Ruff 可能需要整合来自多个来源的 lint.selectlint.ignore,包括当前的 pyproject.toml、任何继承的 pyproject.toml 文件以及 CLI 参数(例如 --select)。

在这种情况下,Ruff 会使用“最高优先级”的 select 作为规则集的基础,然后应用 extend-selectignore 的调整。CLI 选项的优先级高于 pyproject.toml 选项,当前项目的 pyproject.toml 优先级高于任何继承的 pyproject.toml 文件。

例如,给定以下配置文件:

[tool.ruff.lint]
select = ["E", "F"]
ignore = ["F401"]
[lint]
select = ["E", "F"]
ignore = ["F401"]

运行 ruff check --select F401 将导致 Ruff 仅强制执行 F401,不再执行其他规则。

运行 ruff check --extend-select B 将导致 Ruff 强制执行 EFB 规则,但 F401 除外。

自动修复

Ruff 支持对多种代码检查错误进行自动修复。例如,Ruff 可以移除未使用的导入、重新格式化文档字符串、将类型注解重写为较新的 Python 语法等。

要启用修复,请将 --fix 标志传递给 ruff check

$ ruff check --fix

默认情况下,Ruff 将修复所有存在安全修复方案的违规行为;要确定某条规则是否支持修复,请参阅 规则

修复安全性

Ruff 将修复标记为“安全”(safe)和“不安全”(unsafe)。应用安全修复时会保留代码的含义和意图,而应用不安全修复时含义可能会发生变化。

具体而言,不安全修复可能会导致运行时行为改变、删除注释,或两者兼有;而安全修复旨在保留运行时行为,且仅在删除整个语句或表达式时才会删除注释(例如移除未使用的导入)。

例如,unnecessary-iterable-allocation-for-first-element (RUF015) 是一条检查 list(...)[0] 是否存在性能问题的规则。该修复会将此模式替换为 next(iter(...)),这可能会带来显著的速度提升。

$ python -m timeit "head = list(range(99999999))[0]"
1 loop, best of 5: 1.69 sec per loop
$ python -m timeit "head = next(iter(range(99999999)))"
5000000 loops, best of 5: 70.8 nsec per loop

然而,当集合为空时,抛出的异常会从 IndexError 变为 StopIteration

$ python -c 'list(range(0))[0]'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
IndexError: list index out of range
$ python -c 'next(iter(range(0)))'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
StopIteration

由于异常类型的改变可能会破坏上游的错误处理逻辑,该修复被归类为不安全。

Ruff 默认仅启用安全修复。可以通过在配置文件中设置 unsafe-fixes 或向 ruff check 传递 --unsafe-fixes 标志来启用不安全修复。

# Show unsafe fixes
ruff check --unsafe-fixes

# Apply unsafe fixes
ruff check --fix --unsafe-fixes

默认情况下,当有不安全修复可用但未启用时,Ruff 会显示提示。该提示可以通过将 unsafe-fixes 设置设为 false 或使用 --no-unsafe-fixes 标志来关闭。

修复的安全性可以针对特定规则进行调整,使用 lint.extend-safe-fixeslint.extend-unsafe-fixes 设置。

例如,以下配置会将 F601 的不安全修复提升为安全修复,并将 UP034 的安全修复降级为不安全修复:

[tool.ruff.lint]
extend-safe-fixes = ["F601"]
extend-unsafe-fixes = ["UP034"]
[lint]
extend-safe-fixes = ["F601"]
extend-unsafe-fixes = ["UP034"]

您也可以使用前缀来选择规则,例如,使用 F 可以将 Pyflakes 中所有规则的修复提升为安全。

注意

使用 json 输出格式时,Ruff 总是会显示所有修复。每个修复的安全性信息可以在 applicability 字段下找到。

禁用修复

要限制 Ruff 应修复的规则集,请使用 lint.fixablelint.extend-fixablelint.unfixable 设置。

例如,以下配置将启用除 unused-imports (F401) 之外所有规则的修复:

[tool.ruff.lint]
fixable = ["ALL"]
unfixable = ["F401"]
[lint]
fixable = ["ALL"]
unfixable = ["F401"]

相反,以下配置将仅为 F401 启用修复:

[tool.ruff.lint]
fixable = ["F401"]
[lint]
fixable = ["F401"]

错误抑制

Ruff 支持多种机制来抑制代码检查错误,无论是误报还是允许的违规。

配置

要在任何地方忽略某条检查规则,可以通过命令行或在 pyproject.tomlruff.toml 文件中将该规则添加到 lint.ignore 设置的“忽略”列表中。

要根据文件路径前缀或模式在特定文件中忽略检查规则,请参阅 lint.per-file-ignores 设置。

注释

Ruff 支持多种形式的忽略注释,包括行内和文件级的 noqa 注释,以及范围忽略。

行级

Ruff 支持类似于 Flake8noqa 系统。要忽略单个违规,请在行尾添加 # noqa: {code},如下所示:

# Ignore F841.
x = 1  # noqa: F841

# Ignore E741 and F841.
i = 1  # noqa: E741, F841

# Ignore _all_ violations.
x = 1  # noqa

对于多行字符串(如文档字符串),noqa 指令应放在字符串末尾(闭合引号之后),并将应用于整个字符串,如下所示:

"""Lorem ipsum dolor sit amet.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
"""  # noqa: E501

对于导入排序,noqa 应放在导入块第一行的末尾,并将应用于块中的所有导入,如下所示:

import os  # noqa: I001
import abc

完整的行内注释规范如下:

  • 行内全盘 noqa 注释通过对 #noqa 进行不区分大小写的匹配来识别,# 符号后可选空格,其后紧跟:注释结束、新注释开始 (#),或空格后跟除 : 之外的任何字符。
  • 行内规则抑制通过首先查找对 #noqa 的不区分大小写匹配来识别,# 符号后可选空格,noqa 后可选空格,并紧跟 : 符号。之后预期得到一个规则代码列表,由大写 ASCII 字符序列加 ASCII 数字组成,以空格或逗号分隔。列表在最后一个有效代码处结束。我们将尝试解释缺少定界符的规则(例如 F401F841),但在这种情况下会发出警告。

块级

要忽略代码范围或块内的一个或多个违规,可以使用“禁用”注释后跟匹配的“启用”注释,如下所示:

# ruff: disable[E501]
VALUE_1 = "Lorem ipsum dolor sit amet ..."
VALUE_2 = "Lorem ipsum dolor sit amet ..."
VALUE_3 = "Lorem ipsum dolor sit amet ..."
# ruff: enable[E501]

要定义范围,“禁用”和“启用”注释必须具有相同的代码、相同的顺序,以及逻辑代码块内相同的缩进级别。

def foo():
    # ruff: disable[E741, F841]
    i = 1
    # ruff: enable[E741, F841]

如果找不到匹配的“启用”注释,Ruff 也会将其视为“隐式”范围。隐式范围定义为从起始的“禁用”注释开始,直到缩进小于该起始注释的逻辑作用域结束。

def foo():
    # ruff: disable[E741, F841]
    i = 1
    if True:
        O = 1
    l = 1

# implicit end of range
foo()

强烈建议使用显式范围抑制,以防止意外忽略违规,特别是在全局模块作用域中。因此,对于任何隐式范围,都会产生 RUF104 诊断信息。如果确实需要隐式范围抑制,可以禁用 RUF104 规则,或者在“禁用”注释末尾添加行内 noqa 抑制。

范围抑制不能用于启用或选择项目配置或运行时标志未选择的规则。“启用”注释只能用于终止前面具有相同代码的“禁用”注释。

noqa 抑制不同,范围抑制不支持对所有违规的“全盘”抑制。必须至少列出一个违规代码。

完整的范围抑制注释规范如下:

  • 以区分大小写的 #ruff: 开头的独立行注释(#: 符号后可选空格),后跟 disableenable 以分别开启或结束范围,紧接着 [,然后是任何要抑制的代码,并以 ] 结尾。
  • 要抑制的代码必须以逗号分隔,每个代码前后可选空格,最后一个代码后可选一个尾随逗号。

文件级

要忽略整个文件中的所有违规,在文件中的任意位置(最好在顶部)添加一行 # ruff: noqa,如下所示:

# ruff: noqa

要忽略整个文件中的特定规则,在文件中的任意位置(最好在顶部)添加一行 # ruff: noqa: {code},如下所示:

# ruff: noqa: F841

全局 noqa 注释必须位于其自己的行上,以区别于忽略单行违规的注释。

请注意,Ruff 也会尊重 Flake8 的 # flake8: noqa 指令,并将其视为等同于 # ruff: noqa

文件级抑制注释规范如下:

  • 文件级豁免注释通过对 #ruff:#flake8: 进行区分大小写的匹配来识别,# 后和 : 前可选空格,后跟可选空格以及对 noqa 的不区分大小写匹配。之后,其规范与上述行内 noqa 抑制相同。

检测未使用的忽略注释

Ruff 实现了一条特殊规则 unused-noqa(代码为 RUF100),以强制您的抑制是“有效”的,即它们所声称忽略的违规实际上已被触发并被抑制。要标记未使用的抑制注释,请运行 --extend-select RUF100,如下所示:

$ ruff check /path/to/file.py --extend-select RUF100

Ruff 还可以通过其修复功能删除任何未使用的抑制注释。要删除未使用的抑制,请运行 --fix,如下所示:

$ ruff check /path/to/file.py --extend-select RUF100 --fix

自动添加必要的忽略注释

Ruff 可以自动向所有包含违规的行添加 noqa 指令,这在将新代码库迁移到 Ruff 时非常有用。要自动向所有相关行添加 noqa 指令(带有适当的规则代码),请运行 --add-noqa,如下所示:

$ ruff check /path/to/file.py --add-noqa

isort 动作注释

Ruff 尊重 isort 的 动作注释# isort: skip_file# isort: on# isort: off# isort: skip# isort: split),这些注释允许有选择地启用或禁用代码块的导入排序以及其他行内配置。

Ruff 还尊重带有 # ruff: 前缀的这些动作注释变体(例如 # ruff: isort: skip_file# ruff: isort: on 等)。这些变体更清楚地传达了动作注释是针对 Ruff 的,但在功能上等同于 isort 的变体。

与 isort 不同,Ruff 不尊重文档字符串内的动作注释。

有关更多信息,请参阅 isort 文档

退出代码

默认情况下,ruff check 的退出状态码如下:

  • 0:如果未发现违规,或者所有现有违规均已自动修复。
  • 1:如果发现了违规。
  • 2:如果 Ruff 因配置无效、CLI 选项无效或内部错误而异常终止。

这一约定与 ESLint、Prettier 和 RuboCop 等工具一致。

ruff check 支持两个命令行标志来改变其退出代码行为:

  • --exit-zero:即使发现了违规,Ruff 也会以状态码 0 退出。注意,如果 Ruff 异常终止,它仍会以状态码 2 退出。
  • --exit-non-zero-on-fix:即使所有违规均已自动修复,如果发现了违规,Ruff 也会以状态码 1 退出。注意,使用 --exit-non-zero-on-fix 可能会导致即使在修复后没有剩余违规,也会返回非零退出码。