福田
どんなプログラミング言語でも、静的コード解析ツール
Ruffは2022年8月にリリースされた比較的新しい、Pythonのリンター兼フォーマッターです。Ruffはリリースからまだ半年足らずしか経っておりませんが、多くの著名なライブラリで採用[1]され、毎日のようにアップデートされています。2023年3月時点でのRuffの使い方、そしてこれからの発展について、本記事で紹介します。
Ruffとは?
ここではRuffのコンセプトや特徴を紹介します。
Ruffのコンセプト
Ruffは以下の2つの仮説に基づいて作られています。
- Pythonのツールは、より処理性能の高い言語に書き換えられる
- 統合されたツールは、バラバラのツールセットでは得られない効率を得られる
たとえば、JavaScriptの場合、関連するツールがいろいろな言語で書かれています
2つ目の仮説は、現在Pythonで静的コード解析やフォーマットに使われるツールが複数あり、それぞれが構文解析をしているため、もっと効率よくできるのでは?
これらの仮説[2]から、RuffはRustで作られており、また複数のツールにインスパイアされたさまざまなルールが実装されています。現時点では、静的コード解析のツールとして、そして次のステップとして本格的なフォーマッターの機能を拡張していく、というフェーズのようです[3]。
Ruffの特徴
Ruffの特徴として、速度とルールについて説明します。
速度
2月のPython Monthly Topicsで紹介されたPolarsと同じく、RuffもRustで実装されており、高速です。
またパフォーマンスの低下を防ぐため、実行結果をキャッシュしています。速度については簡易的に計測した結果を後述します。
静的コード解析のルール
RuffはFlake8やFlake8のプラグイン、その他多くのリンターにインスパイアされた400以上の静的コード解析のルールが実装されています。デフォルトでは、Flake8に含まれているPyflakes、pycodestyleのルールでチェックします。
- Pyflakes: 不具合の元になりそうな箇所はないかチェック
(エラーコードがFで始まるもの) - pycodestyle: PEP 8に準拠しているかチェック
(エラーコードがE、Wで始まるもの)
他にもさまざまなルールが実装されており、オプションで利用できます。ルールには以下のようなものがあります。さまざまなリンターで提供されているルールと同じチェックが可能です。
- McCabe:循環的複雑度によるコードの複雑さ
- isort:importの順序
- pep8-naming:PEP 8の命名規則
- pydocstyle:docstringのフォーマット
- flake8-bugbear:バグになりそうなコードの検出
ここではすべて紹介できませんので、詳細は公式サイトをご確認ください。
なおRuffは、現時点ではフォーマッターであるBlackと一緒に使用するように設計されています。そのため、Blackのようなフォーマッターを使用した後にチェックが不要なルールの実装は先送りされているようです[4]。
Ruffのインストール
動作確認に使用したPython、Ruffのバージョンは以下のとおりです。RuffはPython 3.
- Python 3.
11. 1 - Ruff 0.
0.252
Ruffはpip
コマンドで簡単にインストールできます。
$ pip install ruff
インストールすると、コマンドラインでruffコマンドを実行できます。
$ ruff --version ruff 0.0.252
Ruffによる静的コード解析とオプション
まずは簡単なコードに静的コード解析を実行して、出力結果を確認してみましょう。 デフォルトの設定で出力される問題点をコメントで記載しています。
このコードに対して、ruffコマンドを実行します。チェックするためのコマンドは ruff check
ですが、 check
は省略可能です。デフォルトでチェックされるルールは、Flake8に含まれているPyflakes、pycodestyleのルールになります。
E401
、F841
というエラーコードと問題点が出力されます。Flake8と同様の出力がされるため、Flake8をご利用の方には見慣れたフォーマットかと思います。
$ ruff test.py test.py:1:1: E401 Multiple imports on one line test.py:1:17: F401 [*] `io` imported but unused test.py:6:5: F841 [*] Local variable `path` is assigned to but never used Found 3 errors. [*] 2 potentially fixable with the --fix option.
問題点を解決するには、ファイルを修正し再度チェックをします。
また別の方法として、出力結果の末尾に [*] 2 potentially fixable with the --fix option.
とあるように、Ruffのコードフォーマット機能--fix
で自動修正も可能です。 コードフォーマットについては後述します。
オプション
Ruffではオプションを利用することで柔軟にルールを指定できます。主なオプションは次のとおりです。すべてのオプションは公式サイトをご確認ください。
オプション | 概要 |
---|---|
select | 指定したルールをチェックの対象とする |
ignore | 指定したルールをチェックの対象としない |
fixable | 指定したルールを修正の対象とする |
unfixable | 指定したルールを修正の対象としない |
exclude | ファイルやディレクトリを除外する |
line-length | 1行の最大文字数を変更する |
オプションはpyproject.
、ruff.
、コマンドラインで指定できます。
pyproject.
でオプションを指定するサンプルは以下のとおりです。Rules - Ruff にある、ルールF
、一部を指定する場合はF401
)
例として、以下のようにselect
とignore
にルールを設定し、先ほどのコードを再度チェックします。出力結果が異なることを確認してみましょう。
この状態で再度ruffコマンドを実行すると、select
に追加したI
とN
のエラーが出力され、ignore
に追加したE
とF
で始まるエラーがなくなります。
$ ruff test.py test.py:1:1: I001 [*] Import block is un-sorted or un-formatted test.py:4:5: N802 Function name `greetingPath` should be lowercase Found 2 errors. [*] 1 potentially fixable with the --fix option.
Ruffのコードフォーマット
Ruffは--fix
によってコードフォーマットができます。
$ ruff --fix
フォーマットの対象となるのは、select
オプションで指定しているルールです。
fixable
オプションでは、select
オプションで指定しているルールのうち、どれを修正対象とするか指定できます。fixable
オプションのデフォルトは、すべてのルールが指定されているため、select
で指定したルールがすべて修正対象となります。
先ほどの、 test.
の最初の出力結果をおさらいしてみましょう。
出力されているエラーのうち、F401 [*] `io` imported but unused
とF841 [*] Local variable `path` is assigned to but never used
がRuffの--fix
で自動的に修正できるエラーです。Ruffで検出されたエラーがすべて修正できるわけではなく、修正できるのは一部ですpyproject.
でのオプションは設定していない状態を想定しています)。
--fix
でフォーマットを実行します。
3つのエラーが見つかり、2つが修正され、1つが残ることがわかります。
修正後のコードは次のようになります。残っているエラーは1行に複数のimport
を記載しているPEP 8違反のエラーになります。
残っているエラーのE401 Multiple imports on one line
を修正するには、isortのルールである、I
をチェック対象に追加します。前述のpyproject.
のselectオプションに"I"
を追加を追加し、Ruffの実行を試してみてください。
コードフォーマットで気になった点
利用した所感として、普段使い慣れているBlackやisortでは修正されないエラーをRuffでは修正してしまう点が、個人的には少し気になりました。気になったのは次の2点です[5]。
- 未使用のインポートの削除 -
test.
py:1:17: F401 [*] `io` imported but unused - 未使用変数の削除 -
test.
py:6:5: F841 [*] Local variable `path` is assigned to but never used
修正をしたくない場合には、設定ファイルにて次のように unfixable
を設定することで回避できます。
そのため、Ruffのフォーマットを利用する場合、以下の方針を検討する必要がありそうです[6]。
- 個人で利用する場合
- Ruffに任せてみる
- 修正箇所を確認してから実行する
- プロジェクトで利用する場合にはメンバーの合意をとる
Ruffでのフォーマットについては、unfixable
オプションを利用しルールやカテゴリを除外するように」
コードフォーマット機能については次のような議論がされており、今後のさらなる改善が期待されます。
Ruffの実行速度について
続いて、Ruffの速度についてみてみましょう。比較はFlake8と行います。計測の対象は手元にあった古いプロジェクトです。ファイル数はおよそ4000ファイル、コードの行数は70万行、指摘の件数は2万件程度になります。
Ruffではまだ実装途中のルール[8]があります。そのため、同等のチェックの結果になるように検出された結果をもとに調整後、 time
コマンドで計測を行いました。
$ time ruff . $ time flake8 .
Flake8に比べて処理にかかった時間が10分の1以下になっています。対象の規模が大きければ大きいほど、処理速度の差を体感できます。
項目 | 結果:Flake8 | 結果:Ruff |
---|---|---|
処理にかかった時間 |
11. |
0. |
ユーザーCPU時間 |
18. |
2. |
システムCPU時間 |
2. |
1. |
小さなファイルでも計測してみると違いがわかります。ぜひ簡単なファイルで試してみてください。
そのほかの機能
さまざまな実行方法
IDEへの対応
RuffはVisual Studio CodeなどのIDEに対応しています。
詳細については以下をご確認ください。
--watch機能
--watch
でファイルの変更を監視できます。
以下のようにディレクトリやファイルを指定し、--watch
でruffコマンドを実行します。ファイルに変更がある場合に自動的にチェックされ、その結果が出力されます。
pre-commitやtox
gitでcommitする際にさまざまなツールを実行するpre-commitや、テストや静的コード解析などをまとめて実行するtoxでの利用も可能です。
flake8からの移行
Ruffの作者の方がFlake8からの移行ツールを公開しています。Flake8の設定を読み込み、Ruff用の設定ファイルを出力するコマンドラインツールです。
詳細については以下をご確認ください。
チェックの例外を設定する - # noqa
RuffではFlake8と同様にnoqa
を利用し、行単位、ファイル単位でルールをチェック対象から除外できます。
またRuffではこのnoqa
を管理する機能があります。
- 独自ルール
RUF100
を指定すると、未使用のnoqa
(関係ないルールが指定されてる) をチェック・ 削除できる --add-noqa
によって、エラー行に対し、自動でnoqa
を付与できる
既存プロジェクトにて、未使用のnoqa
の削除は便利な機能です。詳細については以下を確認してください。
まとめ
Ruffのコンセプトである速度と1つのツールでの利便性は大きなメリットです。また、既存の有名なライブラリでの採用もRuffを選択する後押しとなるでしょう。まずはリンターとして、ぜひ試してみてください。私も少人数のプロジェクトでリンターとして利用を始めました。そこで得られた知見などまた別の機会に共有できればと思います。
Ruffの開発のスピードはとても早く日々アップデートされています[9]。今後のアップデートによって、フォーマッターとしても活躍が期待できます。
また本記事では、現在広く使われているFlake8やBlackの詳細な説明はしませんでした。Ruffと併せて気になる方は、公式ドキュメントや、
Ruffには詳細なコントリビューションガイドがあります。Rustに興味があるPythonエンジニアのみなさま、この機会にコントリビュートをモチベーションにしても良いのではないでしょうか。
本記事を掲載しているgihyo.