Ruff v0.0.285 现已发布。您可以从 PyPI 或您选择的包管理器安装它

pip install --upgrade ruff

提醒一下:Ruff 是一个用 Rust 编写的超高速 Python 代码检查工具。 Ruff 可以替代 Flake8(以及数十个插件)、isort、pydocstyle、pyupgrade 等,其执行速度比任何单个工具快数十或数百倍。

GitHub 上查看完整的变更日志,或继续阅读亮点。

Jupyter Notebooks 支持稳定化

Jupyter Notebooks 的实验性支持已在 v0.0.276 版本中添加到 Ruff。通过对 IPython 魔术命令、Notebook 的 --diff 支持以及对规则行为的一些更改,Ruff 对 Jupyter Notebooks 的支持在 v0.0.285 中被认为是稳定的,并可用于生产环境。

请注意,Jupyter Notebooks 默认情况下仍未进行代码检查,需要通过将其包含在目标路径中进行选择启用。

[tool.ruff]
include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"]

有关详细信息,请参阅关于 Jupyter Notebook 发现的文档

Jupyter Notebook 支持由 @dhruvmanila 贡献。

支持 IPython 魔术命令

IPython 魔术命令是 Jupyter Notebooks 中的一项强大功能,允许您更新 IPython 设置、运行系统命令、访问 Python 帮助菜单等等。最初,Ruff 不支持解析包含魔术命令的代码(通常由前导的 %%%! 指示),因为它们不是有效的 Python 代码。因此,包含魔术命令的单元格在分析期间会被跳过,这可能导致无效诊断。

例如,给定 Notebook 中的以下单元格

单元格 1

import pandas as pd

%matplotlib inline

单元格 2

df = pd.read_csv("foo.csv")

在 Ruff 的先前版本中,由于导入是在跳过的单元格中定义的,因此会在第二个单元格中引发未定义名称 (F821) 违规。现在,Ruff 支持解析 Jupyter Notebooks 中的魔术命令,包含魔术命令的单元格将包含在分析中,从而消除了常见的误报来源。

Jupyter Notebooks 的 Diff 支持

当使用 Ruff 修复违规时,通常使用 --diff 标志预览更改非常有用。然而,由于原始和渲染的 Notebook 文件内容之间的差异,显示 Jupyter Notebooks 的更改具有挑战性。现在,Ruff 会显示渲染单元格的 diff,让您可以轻松验证对 Notebook 的更改。

例如,以下是将 I001F841EM101 的修复应用于 Notebook 的 diff

--- /Users/ruff/notebooks/test.ipynb:cell 1
+++ /Users/ruff/notebooks/test.ipynb:cell 1
@@ -1,2 +1,2 @@
-from pathlib import Path
-import sys
+import sys
+from pathlib import Path
--- /Users/ruff/notebooks/test.ipynb:cell 4
+++ /Users/ruff/notebooks/test.ipynb:cell 4
@@ -1,2 +1,2 @@
 def foo():
-    unused_variable = "hello world"
+    pass
--- /Users/ruff/notebooks/test.ipynb:cell 6
+++ /Users/ruff/notebooks/test.ipynb:cell 6
@@ -1,2 +1,3 @@
 if foo is None:
-    raise ValueError("foo cannot be `None`")
+    msg = "foo cannot be `None`"
+    raise ValueError(msg)

Would fix 3 errors.

Jupyter Notebooks 的规则更改

Jupyter Notebooks 与常规 Python 具有略微不同的语义。在实验期间,我们发现有几个规则需要更新,以便在 Notebook 中表现不同。

如果变量被分配给 Jupyter 魔术命令,unused-variable (F841) 修复将不再删除整个语句。例如,如果您有一个未使用的变量分配给 shell 命令,如下所示

an_unused_variable = !ping google.com

我们以前会建议删除整行,但现在只会建议删除变量以保留副作用

- an_unused_variable = !ping google.com
+ !ping google.com

Jupyter Notebooks 允许您在异步上下文之外 await 异步函数。因此,yield-outside-function (F704) 已更新,允许在 Jupyter Notebooks 中顶层使用 await,而不是引发违规。

规则更改由 @dhruvmanila#6141 中和 @charliermarsh#6607 中贡献。

新规则:pytest-unittest-raises-assertion (PT027)

作用是什么?

检查 unittest 模块中与异常相关的断言方法的使用。

为何重要?

为了强制执行 pytest 推荐的断言风格,推荐使用 pytest.raises 而不是 unittest 中与异常相关的断言方法,例如 assertRaises

例如,给定以下代码片段

import unittest


class TestFoo(unittest.TestCase):
    def test_foo(self):
        with self.assertRaises(ValueError):
            raise ValueError("foo")

使用了 unittest 风格的上下文管理器,但应该使用 pytest 风格的上下文。

import unittest
import pytest


class TestFoo(unittest.TestCase):
    def test_foo(self):
        with pytest.raises(ValueError):
            raise ValueError("foo")

有关背景信息,请参阅pytest 文档:关于预期异常的断言

此规则源自 flake8-pytest-style

@harupy 贡献。

新规则:pytest-duplicate-parametrize-test-cases (PT014)

作用是什么?

检查 pytest.mark.parametrize 中的重复测试用例。

为何重要?

重复的测试用例是多余的,应该删除。

例如,给定以下代码片段

import pytest


@pytest.mark.parametrize(
    ("param1", "param2"),
    [
        (1, 2),
        (1, 2),
    ],
)
def test_foo(param1, param2):
    ...

测试用例 (1, 2) 是重复的,它会增加测试时间而不会增加覆盖率。相反,请省略冗余用例。

import pytest


@pytest.mark.parametrize(
    ("param1", "param2"),
    [
        (1, 2),
    ],
)
def test_foo(param1, param2):
    ...

有关背景信息,请参阅pytest 文档:如何参数化 fixture 和测试函数

此规则源自 flake8-pytest-style

@harupy 贡献。

新规则:banned-module-level-imports (TID253)

作用是什么?

检查模块级别的导入,这些导入应该延迟导入(例如,在函数定义内、if TYPE_CHECKING: 块内或某些其他嵌套上下文中)。

为何重要?

某些模块导入成本较高。例如,导入 torchtensorflow 可能会导致 Python 程序启动时间出现明显的延迟。

在这种情况下,您可能希望强制模块在需要时延迟导入,而不是在文件顶部导入。这可能涉及将导入内联到使用它的函数中,而不是无条件导入,以确保仅在必要时才导入模块。

例如,给定以下代码片段

import tensorflow as tf


def show_version():
    print(tf.__version__)

当您的模块被导入时,tensorflow 将被导入,但它只在调用 show_version 时才需要。相反,请延迟导入。

def show_version():
    import tensorflow as tf

    print(tf.__version__)

此规则要求在 flake8-tidy-imports.banned-module-level-imports 设置中配置模块名称。

此规则源自 flake8-tidy-imports

@durumu 贡献。

新规则:bad-dunder-name (W3201)

此规则可在 Ruff nursery 中使用。

作用是什么?

检查任何拼写错误的双下划线(dunder)方法以及任何以 __...__ 定义但不是预定义方法的方法。

预定义方法涵盖了所有 Python 标准的双下划线方法。

为何重要?

拼写错误的双下划线方法可能导致您的代码无法按预期运行。

由于双下划线方法与自定义 Python 中类的行为相关联,引入一个偏离标准 Python 双下划线方法的双下划线方法(例如 __foo__)可能会使阅读代码的人感到困惑。

例如,给定以下代码片段

class Foo:
    def __init_(self):
        ...

__init__ 方法定义中可能存在拼写错误,它应该写成

class Foo:
    def __init__(self):
        ...

或者给定以下代码片段

class Foo:
    def __bar__(self):
        ...

__bar__ 不是一个已知的双下划线方法,可能不应使用。相反,请将其定义为私有方法。

class Foo:
    def _bar(self):
        ...

此规则源自 pylint

@LaBatata101 贡献。

新规则:subprocess-run-check (W1510)

作用是什么?

检查在没有显式 check 参数的情况下使用 subprocess.run 的情况。

为何重要?

默认情况下,subprocess.run 不会检查其运行进程的返回代码。这可能导致静默失败。

相反,请考虑使用 check=True 在进程失败时引发异常,或显式设置 check=False 以标记此行为是故意的。

例如,给定以下代码片段

import subprocess

subprocess.run(["ls", "nonexistent"])  # No exception raised.

如果命令失败,Python 中不会引发异常。设置 check 以确保检测到失败的命令。

import subprocess

subprocess.run(["ls", "nonexistent"], check=True)  # Raises exception.

或者通过显式将 check 设置为 false 来表明您不关心它是否失败。

import subprocess

subprocess.run(["ls", "nonexistent"], check=False)  # Explicitly no check.

有关背景信息,请参阅Python 文档:subprocess.run

此规则源自 pylint

@tjkuson 贡献。

新规则:quadratic-list-summation (RUF017)

作用是什么?

检查使用 sum() 来展平列表的列表的情况,这具有二次复杂度。

为何重要?

使用 sum() 展平列表的列表是列表数量的二次方,因为 sum() 会为求和中的每个元素创建一个新列表。

相反,请考虑使用其他方法来展平列表,以避免二次复杂度。以下所有方法在列表数量上都是线性的:

  • functools.reduce(operator.iconcat, lists, [])
  • list(itertools.chain.from_iterable(lists)
  • [item for sublist in lists for item in sublist]

例如,给定以下代码片段

lists = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
joined = sum(lists, [])

如果列表很长,展平它们将不会很高效。相反,请考虑使用以下高效实现:

import functools
import operator


lists = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
functools.reduce(operator.iconcat, lists, [])

有关背景信息,请参阅Python 中如何避免展平列表的列表如何将列表的列表展平为单个列表?

@evanrittenhouse 贡献。