测试-告别 Flake8 和 PyLint:使用 Ruff 进行更快的代码检查

目录

告别 Flake8 和 PyLint:使用 Ruff 进行更快的代码检查

Flake8 和 PyLint 是常用且非常有用的代码检查工具:它们可以帮助你发现代码中的潜在错误和其他问题,即所谓的“lints”。但它们也可能很慢。即使它们在你的电脑上运行得很快,在 CI 系统(如 GitHub Actions、GitLab 等)中仍然可能很慢。

幸运的是,现在有一个新的代码检查工具 Ruff,它的速度要快得多。而且它支持许多与 Flake8 相同的检查,包括许多 Flake8 插件的检查。

在本文中,我们将了解为什么 Ruff 的额外检查速度如此有用,尤其是在与替代工具相比时。具体来说,我们将讨论:

  • 一个不在标准 Flake8 中的有用检查。
  • 与 Ruff 实现的速度比较(预览:它快得多!)。
  • 为什么在你的电脑上看似很快的检查在 CI 中可能仍然很慢。
  • Ruff 的最坏情况(预览:它快得离谱)。
  • 为什么 Ruff 可能还不适合你。
  • 通过更改 tox 配置来加速代码检查的额外技巧。

为什么 Flake8 不够用:一个例子

Flake8 本身是一个很好的工具,用于捕捉常见的基本错误。例如:

# example1.py
def f(myvar):
    return myva * 2
$ flake8 example1.py
example1.py:4:12: F821 undefined name 'myva'

然而,它并不能捕捉所有问题。考虑以下程序:

# example2.py
def make_three_adders():
    result = []
    for i in [10, 20, 30]:
        def add(x):
            return x + i
        result.append(add)
    return result


for adder in make_three_adders():
    print(adder(7))

你认为这个程序会输出什么?我们可能会期望它输出 17、27 和 37。但实际上:

$ python example2.py
37
37
37

我们创建的函数并没有捕获 for 循环中变量的当前值,而是使用了最后的值。

这是一个非常适合代码检查工具的场景,但默认情况下 Flake8 不会捕捉到这个问题:

$ flake8 example2.py
$

此时你有三种选择。首先,Flake8 有一个名为 Bugbear 的插件,它添加了许多额外的检查,包括可以识别这个错误的检查:

$ pip install flake8-bugbear
$ flake8 example2.py
example2.py:6:24: B023 Function definition does not bind loop variable 'i'.

其次,PyLint 也可以识别这个问题:

$ pylint example2.py
...
example2.py:6:23: W0640: Cell variable i defined in loop (cell-var-from-loop)
...

最后,你可以使用 Ruff 代码检查工具。

鉴于我在实际项目中见过这个问题导致的错误,我认为至少应该使用这些代码检查工具之一来检查它。


对三种代码检查工具进行基准测试

为了了解这三种代码检查工具在速度上的差异,我测量了在一个包含 12 万行 Python 代码的真实项目上运行这个特定检查所需的时间。我使用了 Python 3.11,在我的 i7-12700K 机器上运行,关闭了超线程和涡轮加速。并且我只运行了这个单一的检查:

$ time pylint --disable=all --enable=cell-var-from-loop src
$ time flake8 --select B023 src
$ time ruff --select B023 src

以下是实际时间和 CPU 使用情况的总结:

工具实际时间(秒)CPU 时间(秒)
Flake8 6.0.0 + bugbear 23.3.231.713.1
PyLint 2.17.314.014.0
Ruff 0.0.2630.21.0

注意到 Ruff 和 Flake8 的 CPU 时间都比实际时间高:这意味着它们利用了多核的优势。

现在,很明显 Ruff 在 CPU 使用上快得多,但实际时间上 Flake8 和 Ruff 的差异并不大,因为 Flake8 可以利用我电脑的所有核心。那么,切换到 Ruff 真的值得吗?


为什么 CI 比我的电脑慢得多(可能也比你的慢)

不幸的是,当在 CI 中运行这个检查时,Flake8+Bugbear 版本的实际时间会比在我的电脑上长得多。这有两个原因:

  1. 更快的核心:我的电脑上的大多数核心是“性能”核心,可能比大多数 CI 系统使用的云虚拟机中的核心快两倍。
  2. 更多的核心:关闭超线程后,我的电脑有 12 个核心可用,假设我没有将它们用于其他任务。而 CI 中使用的云虚拟机通常只有 2 个 vCPU,而“vCPU”是超线程的委婉说法。也就是说,CPU 假装它是 2 个核心,但实际上是一个核心在共享。如果你幸运的话,可能会获得一点并行性,但远不及两个普通核心的性能。

本质上,我们必须假设 CI 运行器几乎没有并行性,并且其 CPU 相当慢。如果上表显示代码检查工具在我的电脑上花费了 14 秒的 CPU 时间,我们可以初步猜测它在 CI 中会花费 30 秒的实际时间。

你的个人电脑可能没有我的快,但即使是较慢的个人电脑,也可能比 2 vCPU 的云虚拟机快得多。


切换到 Ruff:一个真实的例子

考虑到 CI 运行器的速度较慢,Ruff 在 CPU 时间上的大幅减少变得更加有意义。最近,我将一个项目切换到 Ruff;之前它使用的是 Flake8 加上 PyLint 的 cell-var-from-loop 检查。基本上它运行了以下命令:

flake8 src
pylint pylint --disable=all --enable=cell-var-from-loop src

代码检查工具在 CircleCI 的“Medium”运行器上运行,该运行器有 2 个 vCPU。以下是时间对比:

运行时间
原始100 秒
Ruff43 秒
Ruff + tox.ini 调整19 秒

这 19 秒基本上都是开销:每次作业运行时,都需要检出代码、设置虚拟环境、安装 toxruff 等。运行 ruff 的速度如此之快,以至于它几乎不会影响实际时间。


额外加速:更改 tox 配置以进行代码检查

那么,那个节省了 20 秒的 tox.ini 调整是什么呢?默认情况下,tox 在创建新环境时会安装包,在这种情况下是为了进行代码检查。这可能会导致下载依赖项,并且写入大量小文件也需要一些时间。

但对于代码检查,我们根本不需要安装代码。因此,我们可以使用 tox 的 skip_install 选项来跳过安装包代码,从而节省一点时间。


Ruff 会变得多慢?

请注意,除了相对较快的 Flake8 基础设置外,我们只配置了一个额外的检查。在理想情况下,我们希望添加更多的检查;Ruff 默认禁用了许多检查(是否有用取决于你的情况)。

那么,如果我们启用 Ruff 的所有检查,它会花费多少时间呢?实际上,许多检查并不一定是每个人都想启用的,但它仍然为我们提供了一个速度上限:

$ time ruff --select=ALL src/
...
Found 55840 errors.
[*] 17570 potentially fixable with the --fix option.

real    0m0.566s
user    0m1.418s
sys     0m0.172s

即使我们启用了 Ruff 提供的所有检查,它的运行速度仍然比 Flake8 快 10 倍,比 PyLint 更快。


为什么 Ruff 可能还不适合你

Ruff 实现了一长串代码检查规则,直接复制了 Flake8、Flake8 插件、PyLint 和其他工具的规则。PyLint 的兼容性仍处于早期阶段,许多检查缺失;另一方面,使用 PyLint 非常困难且缓慢,以至于大多数检查可能默认都是禁用的。尽管如此,它的进展非常迅速。

对于新项目,我会直接开始使用 Ruff;对于现有项目,我强烈建议在你开始对 CI 中的代码检查速度感到恼火时(甚至在你的电脑上)尝试它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李星星BruceL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值