Ruff 教程:为 Python 项目接入代码检查与格式化

本教程将带你把 Ruff 的代码检查器和格式化器集成到项目中。更详细的介绍见配置 Ruff。

开始使用

首先用 uv 初始化项目:

$ uv init --lib numbers

这个命令创建的 Python 项目结构如下:

numbers
  ├── README.md
  ├── pyproject.toml
  └── src
      └── numbers
          ├── __init__.py
          └── py.typed

接下来清空 src/numbers/__init__.py 中自动生成的内容,并创建 src/numbers/calculate.py,写入以下代码:

from typing import Iterable

import os


def sum_even_numbers(numbers: Iterable[int]) -> int:
    """Given an iterable of integers, return the sum of all even numbers in the iterable."""
    return sum(
        num for num in numbers
        if num % 2 == 0
    )

然后将 Ruff 添加到项目:

$ uv add --dev ruff

现在可以通过 uv run ruff check 对项目运行 Ruff 代码检查器:

$ uv run ruff check
src/numbers/calculate.py:3:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.

Ruff 找到了一个未使用的导入,这是 Python 代码中常见的错误。Ruff 认为它是“可修复”的错误,因此可以运行 ruff check --fix 自动解决:

$ uv run ruff check --fix
Found 1 error (1 fixed, 0 remaining).

运行 git diff 会看到以下差异:

--- a/src/numbers/calculate.py
+++ b/src/numbers/calculate.py
@@ -1,7 +1,5 @@
 from typing import Iterable

-import os
-

def sum_even_numbers(numbers: Iterable[int]) -> int:
    """Given an iterable of integers, return the sum of all even numbers in the iterable."""
    return sum(
        num for num in numbers
        if num % 2 == 0
    )

Ruff 默认在当前目录运行,也可以指定要检查的路径:

$ uv run ruff check src/numbers/calculate.py

项目通过 ruff check 后,就可以通过 ruff format 运行 Ruff 格式化器:

$ uv run ruff format
1 file reformatted

运行 git diff 可以看到,sum 调用被重新排版,符合默认的 88 字符行长度限制:

--- a/src/numbers/calculate.py
+++ b/src/numbers/calculate.py
@@ -3,7 +3,4 @@ from typing import Iterable

 def sum_even_numbers(numbers: Iterable[int]) -> int:
     """Given an iterable of integers, return the sum of all even numbers in the iterable."""
-    return sum(
-        num for num in numbers
-        if num % 2 == 0
-    )
+    return sum(num for num in numbers if num % 2 == 0)

到这里,我们一直在使用 Ruff 的默认配置。接下来看看怎样自定义它的行为。

配置

为了确定每个 Python 文件适用的设置,Ruff 会在该文件所在目录或其任意父目录中,查找首先遇到的 pyproject.toml、ruff.toml 或 .ruff.toml。

在项目根目录中的配置文件添加以下设置:

pyproject.toml

[tool.ruff]
# Set the maximum line length to 79.
line-length = 79

[tool.ruff.lint]
# Add the `line-too-long` rule to the enforced rule set. By default, Ruff omits rules that
# overlap with the use of a formatter, like Black, but we can override this behavior by
# explicitly adding the rule.
extend-select = ["E501"]

ruff.toml

# Set the maximum line length to 79.
line-length = 79

[lint]
# Add the `line-too-long` rule to the enforced rule set. By default, Ruff omits rules that
# overlap with the use of a formatter, like Black, but we can override this behavior by
# explicitly adding the rule.
extend-select = ["E501"]

再次运行 Ruff,就能看到它现在强制执行最大 79 字符的行宽限制:

$ uv run ruff check
src/numbers/calculate.py:5:80: E501 Line too long (90 > 79)
Found 1 error.

支持的全部设置见设置。对于这个项目,还需要明确最低支持的 Python 版本:

pyproject.toml

[project]
# Support Python 3.10+.
requires-python = ">=3.10"

[tool.ruff]
# Set the maximum line length to 79.
line-length = 79

[tool.ruff.lint]
# Add the `line-too-long` rule to the enforced rule set.
extend-select = ["E501"]

ruff.toml

# Support Python 3.10+.
target-version = "py310"
# Set the maximum line length to 79.
line-length = 79

[lint]
# Add the `line-too-long` rule to the enforced rule set.
extend-select = ["E501"]

选择规则

Ruff 支持分布于 50 多个内置插件的900 多条代码检查规则,但合适的规则集合取决于项目需求:有些规则可能太严格,有些只适用于特定框架,等等。

默认情况下,Ruff 启用 F、E、B、UP 和 RUF 等类别中的规则,同时省略与 ruff format 或 Black 等格式化器重叠的风格规则。

如果第一次引入代码检查器,默认规则集是很好的起点:无需配置即可发现广泛的常见错误,例如未使用的导入。完整列表见默认规则。

如果从其他代码检查器迁移到 Ruff,可以启用与先前配置等价的规则。例如,想执行 pyupgrade 规则,可以使用以下配置:

pyproject.toml

[project]
requires-python = ">=3.10"

[tool.ruff.lint]
extend-select = [
  "UP",  # pyupgrade
]

ruff.toml

target-version = "py310"

[lint]
extend-select = [
  "UP",  # pyupgrade
]

再次运行 Ruff,就能看到 pyupgrade 规则得到执行。具体来说,Ruff 会指出使用了已弃用的 typing.Iterable,应改用 collections.abc.Iterable:

$ uv run ruff check
src/numbers/calculate.py:1:1: UP035 [*] Import from `collections.abc` instead: `Iterable`
Found 1 error.
[*] 1 fixable with the `--fix` option.

随着时间推移,我们可能会启用更多规则。例如,希望所有函数都具有文档字符串:

pyproject.toml

[project]
requires-python = ">=3.10"

[tool.ruff.lint]
extend-select = [
  "UP",  # pyupgrade
  "D",   # pydocstyle
]

[tool.ruff.lint.pydocstyle]
convention = "google"

ruff.toml

target-version = "py310"

[lint]
extend-select = [
  "UP",  # pyupgrade
  "D",   # pydocstyle
]

[lint.pydocstyle]
convention = "google"

再次运行 Ruff,就会看到它现在执行 pydocstyle 规则:

$ uv run ruff check
src/numbers/__init__.py:1:1: D104 Missing docstring in public package
src/numbers/calculate.py:1:1: UP035 [*] Import from `collections.abc` instead: `Iterable`
  |
1 | from typing import Iterable
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
  |
  = help: Import from `collections.abc`

src/numbers/calculate.py:1:1: D100 Missing docstring in public module
Found 3 errors.
[*] 1 fixable with the `--fix` option.

忽略错误

在相应行添加 # noqa 注释即可忽略任何代码检查规则。例如,忽略 Iterable 导入的 UP035 规则:

from typing import Iterable  # noqa: UP035


def sum_even_numbers(numbers: Iterable[int]) -> int:
    """Given an iterable of integers, return the sum of all even numbers in the iterable."""
    return sum(num for num in numbers if num % 2 == 0)

再次运行 ruff check,就会看到它不再指出 Iterable 导入问题:

$ uv run ruff check
src/numbers/__init__.py:1:1: D104 Missing docstring in public package
src/numbers/calculate.py:1:1: D100 Missing docstring in public module
Found 2 errors.

如果要为整个文件忽略某条规则,可在文件中的任意位置添加 # ruff: noqa: {code},最好放在靠近文件顶部的位置:

# ruff: noqa: UP035
from typing import Iterable


def sum_even_numbers(numbers: Iterable[int]) -> int:
    """Given an iterable of integers, return the sum of all even numbers in the iterable."""
    return sum(num for num in numbers if num % 2 == 0)

更深入的说明见错误抑制。

添加规则

为现有代码库启用新规则时,可能希望忽略该规则已有的所有违规,只要求今后遵守规则。

Ruff 的 --add-noqa 标志支持这种流程:它根据已有违规情况,为各行添加 # noqa 指令。可将 --add-noqa 与命令行的 --select 标志结合,为所有已有的 UP035 违规添加指令:

$ uv run ruff check --select UP035 --add-noqa .
Added 1 noqa directive.

运行 git diff 可看到以下差异:

diff --git a/numbers/src/numbers/calculate.py b/numbers/src/numbers/calculate.py
index 71fca60c8d..e92d839f1b 100644
--- a/numbers/src/numbers/calculate.py
+++ b/numbers/src/numbers/calculate.py
@@ -1,4 +1,4 @@
-from typing import Iterable
+from typing import Iterable  # noqa: UP035

如果想添加 # ruff: ignore[...] 注释,则使用 --add-ignore 标志。在预览模式中,--add-ignore 使用可读的规则名称代替规则代码。

集成

本教程主要介绍 Ruff 的命令行界面,但也可以通过 ruff-pre-commit 将 Ruff 用作 pre-commit 钩子:

- repo: https://github.com/astral-sh/ruff-pre-commit
  # Ruff version.
  rev: v0.16.10
  hooks:
    # Run the linter.
    - id: ruff-check
    # Run the formatter.
    - id: ruff-format

Ruff 还可以集成到你选择的编辑器。更多信息见编辑器章节。

其他集成方式见集成章节。

来源:Astral Ruff 官方文档 Tutorial;正文译自官方文档源文件。原示例及输出原样保留,未在本地运行。示例 pre-commit 固定版本为 v0.16.10。文档与代码的许可证:MIT;Copyright (c) 2022 Charles Marsh。

MIT License

Copyright (c) 2022 Charles Marsh

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容