PythonのLinter・Formatterとして、既存プロジェクトでは

  • Flake8
  • Black
  • isort

を使用していたのですが、それらの同等の機能を持ちながら更に高速というRuffを知りました。

今回新規プロジェクトに導入を試してみたので、内容をまとめてみようと思います。

動作環境

  • Mac OS Sonoma14.7
  • Python3.12.0
  • FastAPI0.115.4
  • VS Code

導入

Mac OSの場合、下記のいずれかでインストールが可能です。

pip install ruff
brew install ruff

https://docs.astral.sh/ruff/installation/

下記でインストールが完了した事を確認します。

ruff --version
ruff 0.7.1

VS Codeの設定

今回はエディタにVS Codeを使用する為、VS Codeの拡張機能を利用します。

https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff

チームで使用する場合は、推奨拡張機能として.vscode/extensions.jsonに設定しておくと良いでしょう。

{
"recommendations": [
"charliermarsh.ruff"
]
}

続いて、VS Codeでコード保存時に自動的にフォーマットを走らせる為の設定をします。
settings.json

{
// python file
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit"
},
"editor.defaultFormatter": "charliermarsh.ruff"
},

// Ruff
"ruff.organizeImports": false,
"ruff.format.args": [
"--config=pyproject.toml"
]
}

Ruffの設定

プロジェクトディレクトリ直下にpyproject.toml、もしくはruff.tomlを作成する事でRuffが設定ファイルを読み込みます。
今回は、Poetryを使用する為pyproject.tomlに記述する事とします。
pyproject.toml

[tool.ruff]
line-length = 119
indent-width = 4
target-version = "py312"

# Linterの設定
[tool.ruff.lint]
# チェック対象ルール
select = ["ALL"]

# ファイルごとのエラー除外設定
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]

# Formatterの設定
[tool.ruff.format]
quote-style = "double"
indent-style = "space"

それぞれ簡単に解説していきます。
[tool.ruff]:一行の文字数、インデント幅、最小のPythonバージョンを指定しています。
[tool.ruff.lint]ブロック:Ruff公式を参考に、”All”で全てのルールを適用し除外したいルールが出てきた際は都度ルールに追記していくという形を想定し記述しています。

初めてリンターを導入する場合は、デフォルトのルール セットから始めるのが最適です。
これは、範囲が狭く焦点が絞られており、構成なしでさまざまな一般的なエラー (未使用のインポートなど) をキャッチします。

引用:https://docs.astral.sh/ruff/tutorial/#configuration

 

主に使用される他のオプションとしては、下記が存在します。

  • ignore:チェック対象外のルール
  • fixable:修正対象のルール
  • unfixable:修正対象外のルール

[tool.ruff.lint.perf-file-ignores]:__init__.pyでは、”F401″(未使用のインポート)ルールが除外される設定にしています。
[tol.ruff.format]:文字列リテラルにダブルクォート、インデントにスペースを使用するように指定しています。

 

【余談】
ちなみに、RuffはFastAPIやGraphQLといった有名なサービスにも使用実績があります。
例えば、FastAPIであれば下記で確認できるので参考にしてみるのも良いかと思います。

https://github.com/fastapi/fastapi/blob/master/pyproject.toml

試してみる

FastAPIにて、hello worldを返すだけのエンドポイントを作ってみます。
main.py

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
async def hello():
return {"message": "hello world!"}

チェックを実行してみます。

❯ ruff check src/main.py

warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
src/main.py:1:1: D100 Missing docstring in public module
src/main.py:7:11: ANN201 Missing return type annotation for public function `hello`
|
6 | @app.get("/hello")
7 | async def hello():
|           ^^^^^ ANN201
8 |     return {"message": "hello world!"}
|
= help: Add return type annotation

src/main.py:7:11: D103 Missing docstring in public function
|
6 | @app.get("/hello")
7 | async def hello():
|           ^^^^^ D103
8 |     return {"message": "hello world!"}
|

Found 3 errors.

2件のwarningと3件のエラーが確認できます。

warningに関してはお互いのルールが矛盾しているようなので、どちらかをignoreルールに追記します。
D100ANN201については無視したいのでignoreオプションに追記します。

[tool.ruff.lint]
ignore = [
"D100",
"ANN201",
"D400",
"D415",
"D203",
"D213"
]

D103は遵守するためにdocstringをコードに追記します。
下記のようにすることでエラーが無くなりました!

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
async def hello():
"""Hello worldを返す
Returns:
json: hello world
"""
return {"message": "hello world!"}

このように

  • コードを修正する(手動もしくは自動修正 ※後述)
  • ルールを見直す(ignoreに追記等)
    としていくことでコードの品質を保っていく事ができます。

主要なコマンド

ruff check

Linterの設定でカレントディレクトリのチェックが走ります。
※checkの後ろにファイルを指定することも可能
また、 –fixオプションを付けることでRuffで自動修正が可能なコードは修正することができます。

ruff format

コードフォーマットが走ります。
ただし、上述のVS Codeの設定でコード保存時に自動で走る為、コーディング中に使う事はあまり無いかもしれません。

合わせて使用するツールについて

Flake8, black, isortについては完全に置き換えられるパターンが多いようですが、
Pylintについては運用しながら導入を検討した方が良いかもしれません。

Ruff のリンターは Pylint と比べてどうですか?

執筆時点では、Pylint は合計で約 409 個のルールを実装していますが、Ruff は 800 個を超えるルールを実装しており、そのうち少なくとも 209 個は Pylint ルール セットと重複しています (参照: #970 )。

Pylint は Ruff が実装していない多くのルールを実装しており、その逆も同様です。たとえば、Pylint は Ruff よりも多くの型推論を行います (たとえば、Pylint は関数呼び出しの引数の数を検証できます)。したがって、Ruff は Pylint の「純粋な」代替品ではありません (逆も同様)。異なるルール セットを適用するためです。

これらの違いにもかかわらず、多くのユーザー、特にPylint が提供する機能の一部をカバーできる型チェッカーと一緒に Ruff を使用しているユーザーは、Pylint から Ruff への切り替えに成功しています。

引用:https://docs.astral.sh/ruff/faq/#how-does-ruffs-linter-compare-to-pylint

また、下記の通りRuffは型チェッカーではないと名言されているので、型チェックのツール(mypy等)については合わせての使用が推奨されています。

Ruff は Mypy、Pyright、Pyre と比べてどうですか?

Ruff はリンターであり、型チェッカーではありません。型チェッカーと同じ問題の一部を検出できますが、型チェッカーは Ruff が見逃す特定のエラーを検出します。その逆もまた真なりで、Ruff は型チェッカーが通常無視する特定のエラーを検出します。

たとえば、型チェッカーとは異なり、Ruff はソース コード内でそのインポートへの参照を検索して、インポートが使用されていない場合に通知します。一方、型チェッカーは、文字列を期待する関数に整数引数を渡したことをフラグ付けできますが、Ruff はこれを見逃します。これらのツールは補完的です。

Ruff は、Mypy、Pyright、Pyre などの型チェッカーと組み合わせて使用​​することをお勧めします。Ruff は、lint 違反に関するより高速なフィードバックを提供し、型チェッカーは型エラーに関するより詳細なフィードバックを提供します。

引用:https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-mypy-or-pyright-or-pyre

よってmypy等を使ってきたのであれば、引き続き使用をした方が良いでしょう。

最後に

今回はRuffを導入して使用してみました。
多くのツールがまとめられる点や、設定・使い方も簡単でかなり使い勝手が良いと思いました。

また、今回詳細な検証はしておりませんが動作が高速な点においても、CI/CDに組み込む際にも有利に働きそうです。
これから新しくPythonプロジェクトを立ち上げる際は、ぜひとも使ってみてください。