🐍

最近話題になっているPythonリンター兼フォーマッターのRuffを試してみた

2023/12/25に公開

Ruffとは

  • 2022年8月にリリースされた、Rust言語で書かれたPythonのリンター兼フォーマッター。
  • 既存のリンターとフォーマッター(Flake8やBlackなど)に比べて数十倍速い。
  • Pytorch、FastAPI、Amazonを始めとする数多くの有名なOSSや企業が導入。
  • リリースから1年半弱で21k以上のスター⭐を獲得。 ruff_star_history

https://github.com/astral-sh/ruff

なぜ流行っているか

主に以下の3点が大きいでしょう。

  • 圧倒的な速さ
  • さまざまなツールを1つにまとめること
  • 設定はpyproject.tomlだけで済む

Ruff vs. Flake8 + isort + Black

これまでPythonの静的解析や自動整形にはFlake8、isort、Blackを使ってきましたが、これらをRuffに置き換えられるかどうかを調査しながら試してみました。

コンフィグ

現在利用しているFlake8、isort、Blackのコンフィグと、それと同等に動作するRuffのコンフィグは以下の通りです。

Flake8 + isort + Black

.flake8
[flake8]
max-line-length = 119
extend-ignore = E203
exclude =
    .git,
    .mypy_cache,
    .venv
pyproject.toml
[tool.isort]
profile = "black"
line_length = 119
 
[tool.black]
line-length = 119
target-version = ["py311"]

Ruff

pyproject.toml
[tool.ruff]
target-version = "py311"
line-length = 119
exclude = [".mypy_cache"]
 
[tool.ruff.lint]
select = ["E", "W", "F", "I", "C90"]
ignore = ["E203"]

実際に使ってみよう

Sample Code

少し雑な例ですが、いくつかの問題点があるコードを用意しました。
(テストをやりやすくするため、line-lengthを50にしています。)

t.py
import sys
from typing import List
import os
import csv
 
def sum_even_numbers(numbers: List[int]) -> int:
    """Given a list of integers, return the sum of all even numbers in the list."""
    return sum(
        num for num in numbers if num % 2 == 0
    )
def show_exec_info():
    """Show execution info"""
    print(os.path.abspath(__file__))
    print(sys.path)
    

リンター

Flake8 + isort

> .venv/bin/isort t.py --check --diff

ERROR: /workspaces/workflow-hub-api/t.py Imports are incorrectly sorted and/or formatted.
--- /workspaces/workflow-hub-api/t.py:before    2023-12-25 14:50:22.213168
+++ /workspaces/workflow-hub-api/t.py:after     2023-12-25 14:50:46.607020
@@ -1,7 +1,8 @@
+import csv
+import os
 import sys
 from typing import List
-import os
-import csv
+
> flake8 t.py

t.py:4:1: F401 'csv' imported but unused
t.py:6:1: E302 expected 2 blank lines, found 1
t.py:7:51: E501 line too long (83 > 50 characters)
t.py:11:1: E302 expected 2 blank lines, found 0
t.py:15:1: W293 blank line contains whitespace
t.py:15:2: W292 no newline at end of file

Ruff

> ruff check t.py

t.py:1:1: I001 [*] Import block is un-sorted or un-formatted
t.py:4:8: F401 [*] `csv` imported but unused
t.py:7:51: E501 Line too long (83 > 50)
t.py:15:1: W293 [*] Blank line contains whitespace
t.py:15:2: W292 [*] No newline at end of file
Found 5 errors.
[*] 4 fixable with the `--fix` option.

フォマッター

isort + Black

> black t.py

reformatted t.py 
All done!
1 file reformatted.
> isort t.py

Fixing /workspaces/workflow-hub-api/t.py

整形後のコード

t.py
import csv
import os
import sys
from typing import List


def sum_even_numbers(numbers: List[int]) -> int:
    """Given a list of integers, return the sum of all even numbers in the list."""
    return sum(
        num for num in numbers if num % 2 == 0
    )
 
 
def show_exec_info():
    """Show execution info"""
    print(os.path.abspath(__file__))
    print(sys.path)

Ruff

> ruff check t.py --fix
t.py:7:51: E501 Line too long (83 > 50)
Found 4 errors (3 fixed, 1 remaining).
> ruff format t.py

1 file reformatted

整形後のコード

t.py
import os
import sys
from typing import List


def sum_even_numbers(numbers: List[int]) -> int:
    """Given a list of integers, return the sum of all even numbers in the list."""
    return sum(
        num for num in numbers if num % 2 == 0
    )
 
 
def show_exec_info():
    """Show execution info"""
    print(os.path.abspath(__file__))
    print(sys.path)

結果の比較

  • 多くのルール違反の表示には問題がありませんでした。
  • ただし、関数定義の間に2行の空白行を挿入すべきルール(E302)の違反は、Ruffではリンターで表示されませんでしたが、フォーマッターで修正してくれました。
  • isortと比較して、Ruffは使用していないライブラリを勝手に削除してくれました。

考察

上記のE302ルールを表示できなかった問題について、もう少し詳しく調査しました。

Sample Code

t.py
def func1():
    pass
def func2():
    pass

Flake8

> flake8 t.py

t.py:3:1: E302 expected 2 blank lines, found 0

Ruff

> ruff check t.py

前述の例と同様に、Flake8はルール違反を表示できましたが、Ruffでは表示できませんでした。 調査したところ、各ツールがサポートするルールは以下の通りです。

したがって、E3シリーズなど、RuffはFlake8とisortのすべてのルールをカバーしているわけではないことがわかります。

ただし、この問題はすでに解決途中のようです。
https://github.com/astral-sh/ruff/issues/2402

まとめ・感想

  • 多くのツールを1つにまとめることで、やはり手間が大幅に省けます。
  • 基本的な機能についてはすでに十分と言えますが、現時点では一部のルールがサポートされていないため、Flake8 + isort + Blackを完全にRuffに置き換えるわけではありません。
  • コードベースの規模が大きくないと、性能の向上をあまり感じられないかもしれません。
  • 昨年リリースされたばかりのOSSであり、コミュニティは活発ですが、Issueを見るとバグも少なくないです。
  • OSSの成長スピードはかなり速いので、これからの展開に期待します。

Discussion