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

pip install --upgrade ruff

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

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

Jupyter Notebook 支持

Ruff 现已提供对检查 Jupyter Notebooks 的实验性支持。

# Run Ruff over `Notebook.ipynb`.
ruff check Notebook.ipynb

# Re-run Ruff on-save.
ruff check Notebook.ipynb --watch

# Fix any fixable errors in `Notebook.ipynb`.
ruff check Notebook.ipynb --fix

要启用 Jupyter Notebook 文件的代码检查功能,请将 *.ipynb 模式添加到您的 include 设置中,如下所示:

[tool.ruff]
# Allow Ruff to discover `*.ipynb` files.
include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"]

这将提示 Ruff 在任何指定目录中发现 Jupyter Notebook 文件,并相应地对其进行检查。

或者,您也可以直接将 Notebook 文件传递给 ruff。例如,ruff check /path/to/notebook.ipynb 将始终检查 notebook.ipynb

您可以使用 Ruff 的 per-file-ignores 设置在 Jupyter Notebooks 中禁用特定规则。例如,通常允许导入出现在任何单元格中,而不是将其限制在 Notebook 的顶部:

[tool.ruff]
# Allow imports to appear anywhere in Jupyter Notebooks.
per-file-ignores = { "*.ipynb" = ["E402"] }

Jupyter Notebook 支持目前是可选且实验性的,并存在一些已知限制,这些限制将在后续版本中解决(最值得注意的是:不支持混合 Jupyter Magics 和 Python 代码的单元格)。

我们非常希望能得到您的帮助进行测试。有反馈吗?遇到问题了吗?无论是大是小,请随时告知我们

一流的导入解析

Ruff 现在包含一个一流的导入解析器,它基于 Pyright 的导入解析器,支持解析来自命名空间包、类型存根等的导入。

在未来的版本中,该解析器将使我们能够移除各种用户提供的设置(例如 namespace-packages),解析跨文件引用,检测未使用的依赖项等等。

新规则:unnecessary-list-cast (PERF101)

作用是什么?

检查对 for 循环可迭代对象进行显式转换为 list 的情况。

为何重要?

使用 list() 调用来急切地迭代一个已是可迭代的类型(如元组、列表或集合)是低效的,因为它会强制 Python 不必要地创建一个新列表。

移除 list() 调用不会改变代码的行为,但可能会提高性能。

请注意,与所有 perflint 规则一样,这仅作为微观优化,在大多数情况下对性能的影响可以忽略不计。

例如,给定以下代码片段

items = (1, 2, 3)
for i in list(items):
    print(i)

请直接使用可迭代对象

items = (1, 2, 3)
for i in items:
    print(i)

新规则:try-except-in-loop (PERF203)

作用是什么?

检查在 forwhile 循环中使用 try-except 进行异常处理的情况。

为何重要?

通过 try-except 块进行异常处理会产生一些性能开销,无论是否引发异常。

如果可能,请重构代码,将整个循环放入 try-except 块中,而不是将每次迭代都包装在单独的 try-except 块中。

此规则仅适用于 Python 3.11 之前的版本,因为 3.11 版本引入了“零成本”异常处理。

请注意,与所有 perflint 规则一样,这仅作为微观优化,在大多数情况下对性能的影响可以忽略不计。

例如,给定以下代码片段

for i in range(10):
    try:
        print(i * i)
    except:
        break

请将整个循环移入 try-except 块中

try:
    for i in range(10):
        print(i * i)
except:
    pass

新规则:manual-list-comprehension (PERF401)

作用是什么?

检查可以用列表推导式替换的 for 循环。

为何重要?

当使用 for 循环从现有列表创建转换后的列表时,优先使用列表推导式。列表推导式更具可读性且性能更高。

以下为例,列表推导式在 Python 3.11 上快约 10%,在 Python 3.10 上快约 25%。

请注意,与所有 perflint 规则一样,这仅作为微观优化,在大多数情况下对性能的影响可以忽略不计。

例如,给定以下代码片段

original = list(range(10000))
filtered = []
for i in original:
    if i % 2:
        filtered.append(i)

请改用

original = list(range(10000))
filtered = [x for x in original if x % 2]

如果您要向现有列表追加元素,请使用 extend 方法

original = list(range(10000))
filtered.extend(x for x in original if x % 2)

新规则:manual-list-copy (PERF402)

作用是什么?

检查可以用列表复制替换的 for 循环。

为何重要?

当使用 for 循环创建现有列表的副本时,请优先使用 listlist.copy。直接复制更具可读性且性能更高。

以下为例,基于 list 的复制在 Python 3.11 上快约 2 倍。

请注意,与所有 perflint 规则一样,这仅作为微观优化,在大多数情况下对性能的影响可以忽略不计。

例如,给定以下代码片段

original = list(range(10000))
filtered = []
for i in original:
    filtered.append(i)

请改用

original = list(range(10000))
filtered = list(original)

新规则:numpy-deprecated-function (NPY003)

作用是什么?

检查已废弃的 NumPy 函数的使用情况。

为何重要?

NumPy 1.25.0 最终确定了先前版本中引入的各种废弃项。

当 NumPy 函数被废弃时,它们通常会被更新、更高效的版本替换,或者被与 NumPy API 其余部分更一致的函数替换。请优先使用较新的 API,而不是已废弃的 API。

例如,给定以下代码片段

import numpy as np

np.alltrue([True, False])

请改用

import numpy as np

np.all([True, False])

新规则:single-string-slots (PLC0205)

作用是什么?

检查赋值给 __slots__ 的单个字符串。

为何重要?

任何字符串可迭代对象都可以赋值给 __slots__(最常见的是字符串的 tuple)。如果将单个字符串赋值给 __slots__,它将被解释为单个属性名,而不是属性名序列。这可能会导致混淆,因为迭代 __slots__ 值的用户可能期望迭代属性序列,但实际上会迭代字符串中的字符。

要在 __slots__ 中使用单个字符串属性,请将该字符串包装在可迭代容器类型(例如 tuple)中。

例如,给定以下代码片段

class Person:
    __slots__: str = "name"

    def __init__(self, name: str) -> None:
        self.name = name

请改将字符串包装在 tuple

class Person:
    __slots__: tuple[str, ...] = ("name",)

    def __init__(self, name: str) -> None:
        self.name = name

新规则:complex-if-statement-in-stub (PYI002)

作用是什么?

检查存根文件中包含复杂条件表达式的 if 语句。

为何重要?

存根文件支持简单的条件表达式,用于测试 Python 版本和平台差异。然而,类型检查器只理解这些条件表达式的有限子集;复杂的条件表达式可能会导致误报或漏报。

例如,给定以下代码片段

import sys

if (2, 7) < sys.version_info < (3, 5):
    ...

请改用

import sys

if sys.version_info < (3, 5):
    ...

新规则:unrecognized-version-info-check (PYI003)

作用是什么?

检查存根文件中与 sys.version_info 相关的有问题条件。

为何重要?

存根文件支持使用 sys.version_info 测试 Python 版本差异的简单条件表达式。然而,有一些涉及 sys.version_info 比较的常见错误应避免。例如,与字符串进行比较可能导致意外行为。

例如,给定以下代码片段

import sys

if sys.version_info[0] == "2":
    ...

请改用

import sys

if sys.version_info[0] == 2:
    ...

新规则:patch-version-comparison (PYI004)

作用是什么?

检查存根文件中将 Python 版本与补丁版本(例如 Python 3.8.3)而非主版本和次版本(例如 Python 3.8)进行比较的情况。

为何重要?

存根文件支持简单的条件表达式,用于测试 Python 版本和平台差异。然而,类型检查器只理解这些条件表达式的有限子集。特别是,类型检查器不支持补丁版本(例如 Python 3.8.3),只支持主版本和次版本(例如 Python 3.8)。因此,存根文件中的版本检查应仅使用主版本和次版本。

例如,给定以下代码片段

import sys

if sys.version_info >= (3, 4, 3):
    ...

请改用

import sys

if sys.version_info >= (3, 4):
    ...

新规则:wrong-tuple-length-version-comparison (PYI005)

作用是什么?

检查将 Python 版本与长度错误的元组进行比较的情况。

为何重要?

存根文件支持简单的条件表达式,用于测试 Python 版本和平台差异。在与 sys.version_info 进行比较时,避免与长度错误的元组进行比较,这可能导致意外行为。

例如,给定以下代码片段

import sys

if sys.version_info[:2] == (3,):
    ...

请改用

import sys

if sys.version_info[0] == 3:
    ...

错误修复

  • @charliermarsh#5312 中实现:在 mutable-class-default 中支持 pydantic.BaseSettings
  • @charliermarsh#5314 中实现:允许在 mutable-class-default 中赋值 __slots__
  • @charliermarsh#5319 中实现:移除 f-string 前缀时避免语法错误
  • @charliermarsh#5392 中实现:在 iteration-over-set 中忽略解包
  • @dhruvmanila#5383 中实现:在 D407 中将等长等号线替换为破折号线
  • @intgr#5405 中实现:从 PYI053 中排除文档字符串
  • @charliermarsh#5430 中实现:在阴影上下文中对 E731 使用“手动”修复能力
  • @charliermarsh#5467 中实现:检测连续的、非换行符分隔的 NumPy 部分
  • @harupy#5478 中实现:修复 unnecessary-encode-utf8 以正确处理括号内字符串的 encode
  • @charliermarsh#5490 中实现:允许在存根中进行 Final 赋值
  • @charliermarsh#5315 中实现:在分类函数类型时尊重 abc 装饰器
  • @mayrholu#4903 中实现:允许 flake8-todos 中“缺少作者”规则使用 @Author 格式
  • @dhruvmanila#5344 中实现:为 RUF013 忽略类型别名
  • @hauntsaninja#5352 中实现:将 W605 自动修复更改为尽可能使用原始字符串
  • @charliermarsh#5358 中实现:迁移到原始字符串时添加空格
  • @charliermarsh#5359 中实现:更新 invalid-escape-sequence 规则
  • @charliermarsh#5466 中实现:在 B017 规则中包含 BaseException
  • @charliermarsh#5469 中实现:[flake8-django] 在 DJ012 中跳过重复违规