2023年3月: 新しい静的コード解析ツール「Ruff」をご紹介(fukuda)¶
福田(@JunyaFff)です。今月の「Python Monthly Topics」は、最近私が個人的に気になっている静的コード解析ツール Ruff について紹介します。
どんなプログラミング言語でも、静的コード解析ツール(リンター)やフォーマッターは非常に便利です。Pythonでコードを書く場合、皆さんはどんなツールを使っているでしょうか?Flake8やBlack、isortなどが人気で、世界中で多くのPythonエンジニアに利用されています。
Ruffは2022年8月にリリースされた比較的新しい、Pythonのリンター兼フォーマッターです。Ruffはリリースからまだ半年足らずしか経っておりませんが、多くの著名なライブラリで採用 [1] され、毎日のようにアップデートされています。2023年3月時点でのRuffの使い方、そしてこれからの発展について、本記事で紹介します。
Ruffとは?¶
ここではRuffのコンセプトや特徴を紹介します。
Ruffのコンセプト¶
Ruffは以下の2つの仮説に基づいて作られています。
Pythonのツールは、より処理性能の高い言語に書き換えられる
統合されたツールは、バラバラのツールセットでは得られない効率を得られる
たとえば、JavaScriptの場合、関連するツールがいろいろな言語で書かれています(esbuildがGo、SWCがRust、BunがZigなど)。Pythonも同じ発想で関連するツールを別の言語で実装すれば高速化できるのでは、というのが1つ目の仮説です。
2つ目の仮説は、現在Pythonで静的コード解析やフォーマットに使われるツールが複数あり、それぞれが構文解析をしているため、もっと効率よくできるのでは?という点です。たとえば、Blackでコードフォーマットをし、isortでimportの順序を修正した後にFlake8で静的コード解析をすることがあります(Flake8はさらに3つの静的コード解析ツールを1つにまとめたものです)。それぞれのツールがそれぞれソースコードをチェックしています。Ruffは1回の処理で目立ったパフォーマンスの低下なく、すべてのチェック・自動修正が行われることを目標の1つにしています。
これらの仮説から、RuffはRustで作られており、また複数のツールにインスパイアされたさまざまなルールが実装されています。 [2] 現時点では、静的コード解析のツールとして、そして次のステップとして本格的なフォーマッターの機能を拡張していく、というフェーズのようです。[3]
Ruffの特徴¶
Ruffの特徴として、速度とルールについて説明します。
速度¶
2月のPython Monthly Topicsで紹介されたPolarsと同じく、RuffもRustで実装されており、高速です。

引用元: CPythonに対しリンターを実行した速度 https://beta.ruff.rs/docs/
ベンチマークの計測について、公式ドキュメントにも記載があります。 Benchmarks - Ruff¶
またパフォーマンスの低下を防ぐため、実行結果をキャッシュしています。 速度については簡易的に計測した結果を後述します。
静的コード解析のルール¶
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.7以上をサポートしています。
Python 3.11.1
Ruff 0.0.252
Ruffは pip
コマンドで簡単にインストールできます。
$ pip install ruff
インストールすると、コマンドラインでruffコマンドを実行できます。
$ ruff --version
ruff 0.0.252
Ruffによる静的コード解析とオプション¶
まずは簡単なコードに静的コード解析を実行して、出力結果を確認してみましょう。
デフォルトの設定で出力される問題点をコメントで記載しています。
import os, sys, io # 問題点1:1行に複数のインポート 問題点2:ioをインポートしているが未使用
def greetingPath(msg, name):
print(sys.argv)
path = os.get_exec_path() # 問題点3:path変数が未使用
print(f"{msg}, {name}")
このコードに対して、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行の最大文字数を変更する(デフォルトは88) |
オプションは pyproject.toml
、 ruff.toml
、コマンドラインで指定できます。
pyproject.toml
でオプションを指定するサンプルは以下のとおりです。Rules - Ruff にある、ルール(たとえばPyflakesのルール全体を対象とする場合は F
、一部を指定する場合はF401
)を指定します。
例として以下のように select
と ignore
にルールを設定し、先ほどのコードを再度チェックします。
出力結果が異なることを確認してみましょう。
[tool.ruff]
select = ["I", "N"] # "I"はisort, "N"はpep8-naming
ignore = ["E", "F"] # "E"はpycodestyleのError, "F"はPyflakes
この状態で再度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.py
の最初の出力結果をおさらいしてみましょう。
出力されているエラーのうち、 F401 [*] `io` imported but unused
と F841 [*] Local variable `path` is assigned to but never used
がRuffの --fix
で自動的に修正できるエラーです。Ruffで検出されたエラーがすべて修正できるわけではなく、修正できるのは一部です。
( pyproject.toml
でのオプションは設定していない状態を想定しています)。
import os, sys, io
def greeting_path(msg, name):
print(sys.argv)
path = os.get_exec_path()
print(f"{msg}, {name}")
$ 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.
--fix
でフォーマットを実行します。
3つのエラーが見つかり、2つが修正され、1つが残ることが分かります。
$ ruff --fix test.py
ruff --fix test.py
test.py:1:1: E401 Multiple imports on one line
Found 3 errors (2 fixed, 1 remaining).
修正後のコードは次のようになります。残っているエラーは1行に複数の import
を記載しているPEP 8違反のエラーになります。
import os, sys
def greeting_path(msg, name):
print(sys.argv)
os.get_exec_path()
print(f"{msg}, {name}")
残っているエラーの E401 Multiple imports on one line
を修正するには、isortのルールである、I
をチェック対象に追加します。
前述の pyproject.toml
の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
を設定することで回避できます。
[tool.ruff]
select = ["E", "F", "I"] # "I" はisort
unfixable = ["F401", "F841"]
そのため、Ruffのフォーマットを利用する場合、以下の方針を検討する必要がありそうです。[6]
個人で利用する場合
Ruffに任せてみる
修正箇所を確認してから実行する
プロジェクトで利用する場合にはメンバーの合意をとる
Ruffでのフォーマットについては、「現時点ではベストエフォートな修正」とFAQ [7] に記載があります。
また、「フォーマットが強いと感じる場合、 unfixable
オプションを利用しルールやカテゴリを除外するように」ともありました。
オートフォーマット機能については次のような議論がされており、今後のさらなる改善が期待されます。
Ruffの実行速度について¶
続いて、Ruffの速度についてみてみましょう。比較はFlake8と行います。 計測の対象は手元にあった古いプロジェクトです。ファイル数はおよそ4000ファイル、コードの行数は70万行、指摘の件数は2万件程度になります。
Ruffではまだ実装途中のルール[8]があります。そのため、同等のチェックの結果になるように検出された結果をもとに調整後、 time
コマンドで計測を行いました。
$ time ruff .
$ time flake8 .
Flake8に比べて処理にかかった時間が10分の1以下になっています。対象の規模が大きければ大きいほど、処理速度の差を体感できます。
項目 |
結果: Flake8 |
結果: Ruff |
---|---|---|
処理にかかった時間(real) |
11.9s |
0.7s |
ユーザーCPU時間(user) |
18.1s |
2.2s |
システムCPU時間(sys) |
2.3s |
1.1s |
小さなファイルでも計測してみると違いが分かります。ぜひ簡単なファイルで試してみてください。
そのほかの機能¶
さまざまな実行方法¶
IDEへの対応¶
RuffはVisual Studio CodeなどのIDEに対応しています。
詳細については以下をご確認ください。
--watch 機能¶
--watch
でファイルの変更を監視できます。
以下のようにディレクトリやファイルを指定し --watch
でruffコマンドを実行します。ファイルに変更がある場合に自動的にチェックされ、その結果が出力されます。
$ ruff . --watch
# ファイルの変更を検知しチェック
[03:06:25 PM] File change detected...
# エラーを出力
[03:06:25 PM] Found 2 errors. Watching for file changes.
test.py:1:1: E401 Multiple imports on one line
test.py:1:1: I001 [*] Import block is un-sorted or un-formatted
# ファイルの変更を検知しチェック
[03:06:42 PM] File change detected...
# エラーがなくなったことを出力
[03:06:42 PM] Found 0 errors. Watching for file changes.
...
pre-commit や tox¶
gitでcommitする際にさまざまなツールを実行する pre-commit や、テストや静的コード解析などをまとめて実行する toxでの利用も可能です。
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.252'
hooks:
- id: ruff
flake8からの移行¶
Ruffの作者の方がFlake8からの移行ツールを公開しています。 Flake8の設定を読み込み、Ruff用の設定ファイルを出力するコマンドラインツールです。
$ pip install flake8-to-ruff
$ flake8-to-ruff path/to/.flake8
詳細については以下をご確認ください。
チェックの例外を設定する - # noqa¶
RuffではFlake8と同様に noqa
を利用し、行単位、ファイル単位でルールをチェック対象から除外できます。
x = 1 # noqa: F841
# ruff: noqa
import os
...
またRuffではこの noqa
を管理する機能があります。
独自ルール
RUF100
を指定すると、未使用のnoqa
(関係ないルールが指定されてる)をチェック・削除できる--add-noqa
によって、エラー行に対し、自動でnoqa
を付与できる
既存プロジェクトにて、未使用の noqa
の削除は便利な機能です。詳細については以下を確認してください。
まとめ¶
Ruffのコンセプトである速度と1つのツールでの利便性は大きなメリットです。また、既存の有名なライブラリでの採用もRuffを選択する後押しとなるでしょう。 まずはリンターとして、ぜひ試してみてください。わたしも少人数のプロジェクトでリンターとして利用を始めました。そこで得られた知見などまた別の機会に共有できればと思います。
Ruffの開発のスピードはとても早く日々アップデートされています。[9] 今後のアップデートによって、フォーマッターとしても活躍が期待できます。
また本記事では、現在広く使われているFlake8やBlackの詳細な説明はしませんでした。Ruffと併せて気になる方は、公式ドキュメントや、Pythonエンジニア育成推進協会監修 Python実践レシピ|技術評論社をご参考いただけると幸いです(PEP 8やFlake8、Blackについて、わたしが担当しました)。
Ruffには詳細なコントリビューションガイドがあります。Rustに興味があるPythonエンジニアのみなさま、この機会にコントリビュートをモチベーションにしても良いのではないでしょうか。
本記事を掲載しているgihyo.jpでは、Rustの連載もあるようです。こちらも併せてチェック!