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

pip install --upgrade ruff

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

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

Ruff 的词法分析器速度提升2-3倍

Ruff 的词法分析器现在比以前的版本快2-3倍

group                       v0.0.280                               v0.0.281
-----                       --------                               --------
lexer/large/dataset.py      2.18    665.9±5.64µs    61.1 MB/sec    1.00    304.9±3.79µs   133.4 MB/sec
lexer/numpy/ctypeslib.py    2.39    154.4±0.84µs   107.8 MB/sec    1.00     64.5±0.61µs   258.1 MB/sec
lexer/numpy/globals.py      2.89     18.1±0.14µs   163.3 MB/sec    1.00      6.3±0.06µs   471.8 MB/sec
lexer/pydantic/types.py     2.57    326.4±2.23µs    78.1 MB/sec    1.00    127.2±0.71µs   200.5 MB/sec

词法分析器负责将 Python 源代码标记化为令牌流,然后解析器使用这些令牌流构建抽象语法树 (AST)。词法分析器是 Ruff 分析管道的第一步,并在 Ruff 分析的每个文件上运行。

提升词法分析器性能不仅能提高 linter 性能,还将改善未来利用 Ruff 词法分析器的工具的性能,例如 Ruff 的格式化工具。

新的词法分析器利用了更缓存友好的数据结构,执行更少的分配,并包含针对纯 ASCII 源代码的优化。更多详情请参见 #38

行尾的 # ruff: noqa 注释现在将被忽略

Ruff 允许通过 # ruff: noqa 抑制注释在整个文件中禁用 lint 规则。例如,以下内容将忽略文件中所有 lint 错误:

# ruff: noqa
import os
import sys

类似地,以下内容将忽略文件中所有未使用的导入 (F401) 错误:

# ruff: noqa: F401
import os
import sys

过去,Ruff 甚至在这些抑制注释出现在行尾时也会遵守,例如:

import os  # ruff: noqa: F401
import sys

此类行尾注释很可能是一个错误(相反,应使用 # noqa: F401 来抑制单行错误)。Ruff 现在将对这些行尾的文件级抑制注释发出警告并忽略它们。

新规则:unused-private-type-var (PYI018)

作用是什么?

检查是否存在未使用的私有 TypeVar 声明。

为何重要?

已定义但未使用的私有 TypeVar 很可能是错误,应使用、公开或移除以避免混淆。

例如,给定以下代码片段

import typing

_T = typing.TypeVar("_T")

TypeVar 未在任何地方使用,因此应使用、公开或移除。

此规则源自 flake8-pyi

@LaBatata101 贡献。

新规则:unused-private-protocol (PYI046)

作用是什么?

检查是否存在未使用的私有 typing.Protocol 定义。

为何重要?

已定义但未使用的私有 typing.Protocol 很可能是错误,应使用、公开或移除以避免混淆。

例如,给定以下代码片段

import typing


class _PrivateProtocol(typing.Protocol):
    field: int

_PrivateProtocol 类未在任何地方使用,因此应使用、公开或移除。

此规则源自 flake8-pyi

@LaBatata101 贡献。

新规则:unused-private-type-alias (PYI047)

作用是什么?

检查是否存在未使用的私有 typing.TypeAlias 定义。

为何重要?

已定义但未使用的私有 typing.TypeAlias 很可能是错误,应使用、公开或移除以避免混淆。

例如,给定以下代码片段

import typing

_UnusedTypeAlias: typing.TypeAlias = int

此类型别名未在任何地方使用,因此应使用、公开或移除。

此规则源自 flake8-pyi

@LaBatata101 贡献。

新规则:unused-private-typed-dict (PYI049)

作用是什么?

检查是否存在未使用的私有 typing.TypedDict 定义。

为何重要?

已定义但未使用的私有 typing.TypedDict 很可能是错误,应使用、公开或移除以避免混淆。

例如,给定以下代码片段

import typing


class _UnusedPrivateTypedDict(typing.TypedDict):
    field: list[int]

__UnusedPrivateTypedDict 类未在任何地方使用,因此应使用、公开或移除。

此规则源自 flake8-pyi

@LaBatata101 贡献。

新规则:unsupported-method-call-on-all (PYI056)

作用是什么?

检查 appendextendremove 方法是否未在 __all__ 上调用。

为何重要?

不同的类型检查器对在 __all__ 上调用这些方法的支持程度不同。相反,使用 += 运算符向 __all__ 添加项目,已知所有主要的类型检查器都支持这种方式。

例如,给定以下代码片段

__all__ = ["A"]
__all__.append("B")

相反,请使用增广赋值语句将项目添加到 __all__

__all__ = ["A"]
__all__ += ["B"]

此规则源自 flake8-pyi

@LaBatata101 贡献。

新规则:self-assigning-variable (PLW0127)

作用是什么?

检查变量的自赋值。

为何重要?

变量的自赋值是冗余的,很可能是错误。

例如,给定以下代码片段

country = "Poland"
country = country

相反,直接使用变量

country = "Poland"

此规则源自 Pylint

@tjkuson 贡献。

新规则:subprocess-popen-preexec-fn (PLW1509)

作用是什么?

检查 subprocess.Popenpreexec_fn 参数的使用。

为何重要?

preexec_fn 参数在线程内不安全,因为它可能导致死锁。此外,preexec_fn 计划被弃用

相反,请考虑使用特定于任务的参数,例如 envstart_new_sessionprocess_group。这些参数不容易发生死锁,并且更明确。

例如,给定以下代码片段

import os, subprocess

subprocess.Popen(foo, preexec_fn=os.setsid)
subprocess.Popen(bar, preexec_fn=os.setpgid(0, 0))

相反,使用 start_new_sessionprocess_group 等参数

import subprocess

subprocess.Popen(foo, start_new_session=True)
subprocess.Popen(bar, process_group=0)  # Introduced in Python 3.11

此规则源自 Pylint

@tjkuson 贡献。

新规则:os-sep-split (PTH206)

作用是什么?

检查 .split(os.sep) 的使用。

为何重要?

标准库中的 pathlib 模块应该用于路径操作。它提供了一个高级 API,具有对 Path 对象执行常见操作所需的功能。

例如,给定以下代码片段

如果不需要路径的所有部分,则应使用 Path 对象的 nameparent 属性。否则,可以使用 parts 属性,如最后一个示例所示。

import os

"path/to/file_name.txt".split(os.sep)[-1]

"path/to/file_name.txt".split(os.sep)[-2]

if any(part in blocklist for part in "my/file/path".split(os.sep)):
    ...

相反,使用 Path 对象

from pathlib import Path

Path("path/to/file_name.txt").name

Path("path/to/file_name.txt").parent.name

if any(part in blocklist for part in Path("my/file/path").parts):
    ...

@sbrugman 贡献。

新规则:glob (PTH207)

作用是什么?

检查 globiglob 的使用。

为何重要?

osglob 提供的低级 API 相比,pathlib 提供了用于路径操作的高级 API。

在可能的情况下,使用 Path 对象方法(如 Path.glob())可以提高可读性,优于其低级对应项(例如,glob.glob())。

请注意,glob.globPath.glob 并非完全等同

globPath.glob
隐藏文件默认情况下排除隐藏文件。从 Python 3.11 开始,可以使用 include_hidden 关键字来包含隐藏目录。默认情况下包含隐藏文件。
迭代器iglob 返回一个迭代器。在底层,glob 只是将迭代器转换为列表。Path.glob 返回一个迭代器。
工作目录glob 接受 root_dir 关键字来设置当前工作目录。Path.rglob 可用于返回相对路径。
双星号通配符(**glob 需要将 recursive 标志设置为 True,以便 ** 模式匹配任何文件以及零个或多个目录、子目录和符号链接。Path.glob 中的 ** 模式表示“此目录及其所有子目录,递归地”。换句话说,它启用了递归全局匹配。

例如,给定以下代码片段

import glob
import os

glob.glob(os.path.join(path, "requirements*.txt"))

相反,使用 Path 对象

from pathlib import Path

Path(path).glob("requirements*.txt")

@sbrugman 贡献。