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”で全てのルールを適用し除外したいルールが出てきた際は都度ルールに追記していくという形を想定し記述しています。
初めてリンターを導入する場合は、デフォルトのルール セットから始めるのが最適です。
これは、範囲が狭く焦点が絞られており、構成なしでさまざまな一般的なエラー (未使用のインポートなど) をキャッチします。
主に使用される他のオプションとしては、下記が存在します。
- 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ルールに追記します。D100
、ANN201
については無視したいので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プロジェクトを立ち上げる際は、ぜひとも使ってみてください。