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 的更改。
例如,以下是将 I001
、F841
和 EM101
的修复应用于 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:
块内或某些其他嵌套上下文中)。
为何重要? #
某些模块导入成本较高。例如,导入 torch
或 tensorflow
可能会导致 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 贡献。