はじめに
Pythonでアプリケーション開発をするとなると、ちょっとしたPythonスクリプトを書くのとは違った難しさがあります。素のPythonは、スクリプト言語らしい自由さを持っていますが、その状態でアプリケーションを開発をするとなると、コードの品質を維持するのがとても難しいです。
(特に、チーム開発するとなったら、なおさらです。)
となると、Pythonの言語機能のみではそうした状況を何とかするのは難しいので、周辺ツールを使ってコードの品質を維持していくことになります。
具体的には、リンターを通したり、静的型チェック、コードフォーマットをすることになると思います。これまでは、リンターはPylintやFlake8、静的型チェッカーはmypy、フォーマッターはBlackとIsortを使用することが多いと思いますが、ここ最近は取り巻くツールに変化が起きています。
そこで、今回改めて最新のツールを調べてみたところ、「Ruff」と「Pyright」を使うのが現状の最適解だと思ったので、調べた内容を紹介したいと思います。
なお、今回はryeで管理しているプロジェクトに導入しています。ryeの説明は以前の記事もご覧ください。
各ツールの目的
Ruff
RuffはPythonのリンター・コードフォーマッタで、以下のような特徴があります。
- Flake8、Isort、Blackといった既存のリンターやフォーマッタに代替えを目指している。
- Rustで実装されているため、実行速度が速いと謳われている。
まず、リンター機能は、Flake8相当の実装はされているものの、Pylint相当の実装には追いつき中です。
RuffへのPylintルールの実装状況は以下のIssueでトラックされています。
https://github.com/astral-sh/ruff/issues/970
記事執筆時点では、重要そうなルールの実装が進んでいて、そろそろPylintユーザも乗り換えを検討して見ても良いかもしれません。
中には、エラーレベルのルールが実装されていないこともありますが、後で紹介する型チェッカーを使えばチェックでき、そうした流れで実装優先度が高くないのかもしれません。
例えば、
no-member
(クラスに存在しないフィールドやメソッドにアクセス) や、
undefined-variable
(未定義変数へのアクセス) といったものはRuffには実装されていません。
また、一部のルールはプレビュー状態であり、
--preview
フラグをつける必要があること手に注意してください。例えば、
open("file.txt", encoding="utf-8")
というようにファイルオープン時の文字コードに関するルールは、まだプレビュー段階です。
https://docs.astral.sh/ruff/rules/unspecified-encoding/
次に、フォーマッタ機能ですが、Isort相当のインポート文ソートと、Black相当のフォーマッタが実装されています。既存フォーマッタと完全に同じ挙動になるわけではありませんが、こちらは十分実用的です。
Pylint (参考)
汎用的なリンターで、以下のような特徴があります。
- PEP8(Pythonのコーディングガイドライン)への準拠チェックができる。(これはRuffでもできます)
- バグの原因となる紛らわしいコードや、パフォーマンスの改善が見込まれるコードの指摘
- 様々なチェックをしてくれる反面、チェックに時間がかかる
他のどんなリンターよりも細かく厳しくチェックされるため、Pylintを導入し、常に10点満点(指摘なし)を取得できるコードを書けば、そこそこ統制の取れたコードを維持できると思います。
もちろん、設定ファイルによって、ルールの無効化や各種変数(ローカル変数の数など)を設定できるので、プロジェクトにとってただ時間を消耗するだけのルールは適宜無効化することができます。(例えばクラスコメントの矯正など)
一方、オープンソース界隈では、ライバルのRuffやFlake8の方が人気があります。その理由はリンターの実行時間であり、プロジェクトが巨大化するとリンターを実行するに時間がかかる点が、Pylintの弱点です。
確かに、VSCode上で使っていても、ファイルをセーブしてからワンテンポ遅れて表示が反映されることがあります。
また、RuffへのPylintルールの実装も進んでいることから、Pylintへの強いこだわりがなければRuffを使うのが良いでしょう。
ちなみに、文字列結合にforループを使うのではなく、
"".join()
を使う方がパフォーマンスが良いという指摘は、今のところRuffでもFlake8でも出すことはできません。
https://pylint.readthedocs.io/en/latest/user_guide/messages/refactor/consider-using-join.html
Pyright
静的型チェッカーで、以下のような特徴があります。
- 動作が高速
- mypyに比べ、強力な型推論をサポート
- VSCodeの公式PythonプラグインとセットでインストールされるPylanceに同梱
(PyCharmでもサードパーティからPyright用プラグインが提供されている)
Pythonの静的型チェッカーの先駆者であるmypyと比較されがちですが、Pyrightとmypyは目指している方向そのものが異なります。
mypyは、どちらかというと保守的で、厳格なルールを適用すると静的型付け言語のような書き味になります。
一方、Pyrightは強力な型推論エンジンによって、Pythonらしい書き味を残しつつも、型チェックを行います。
例えば、以下のような違いがあります。
- mypyでは変数の型の変化を許容しないが、Pyrightは途中の型変化を許容し、型が確実に変化する場合は変化後の値、if文などで変化する可能性がある場合は、以降Unionになる。
- mypyでは関数の型アノテーションを強制した場合、戻り値の型も必ず書く(たとえNoneであっても)必要があるが、Pyrightでは仮引数の型アノテーションを強制しつつ、戻り値は型推論させることができる。
mypyとpylanceの比較は以下に紹介されています。
https://microsoft.github.io/pyright/#/mypy-comparison
https://future-architect.github.io/articles/20220301a/
各ツールの設定 (VSCodeでの使用例)
Ruff、PyrightともにVSCode向けには各開発元からの公式プラグインがあるため、それを使用します。(ちなみに、PyCharm向けにはサードパーティからプラグインがリリースされています。)
ちなみに、他ワークスペースなどで使用するために競合するプラグイン(PylintやBlackなど)が入っている場合は、ワークスペース単位で無効化しておくことをおすすめします。
Ruff
マーケットプレイスからRuffプラグインをインストールします。Ruffの設定は、
pyproject.toml
または
ruff.toml
に書くことができます。今回は
pyproject.toml
に書いてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | [project] requires-python = ">= 3.12" # プロジェクト設定のPythonバージョンがRuffでも参照される [tool.ruff.lint] preview = true # プレビュー中のルールを有効化 select = [ # pycodestyle "E", # Pyflakes "F", # pyupgrade "UP", # flake8-bugbear "B", # flake8-simplify "SIM", # isort "I", # Pylint "PL", ] |
様々な設定をすることができますが、その中でもPythonのバージョン指定とリンター設定を紹介します。
まず、Pythonのバージョン指定は、
project.requires-python
を参照するようになっています。この設定を元に、バージョンに応じた提案をしてくれるようになるため、もし古いPythonをターゲットとしている場合は、Pythonのバージョンアップを検討しても良いでしょう。
次は、リンター設定です。(こちらも一部のみの紹介です。)
select
に使いたいルールを指定します。ルールのプレフィックスを指定することで、そのグループの全てを有効化することができます。
Ruffに実装されている全てのルールはこちらで確認することができます。
https://docs.astral.sh/ruff/rules/
今回は、以下のページに記載されているおすすめ設定に加え、Pylintのルールを有効化していため
PL
を末尾に加えています。
https://docs.astral.sh/ruff/linter/#rule-selection
さらに、Pylintルールはまだプレビュー中のものが多くあるため、
preview = true
としています。
Flake8のみならず、Flake8の各種プラグインやpyupgradeで実装されているルール、FastAPI固有のルールなど、様々なルールが実装されています。これまでは、リンターの得意領域ごとに使い分ける必要がありましたが、Ruffひとつで様々な種類のリンターがかけられるのはとても便利です!
最後に、VSCode側の設定です。
1 2 3 4 5 6 7 8 9 10 | { "[python]": { "editor.formatOnSave": true, // お好みで "editor.defaultFormatter": "charliermarsh.ruff", // Ruffをデフォルトフォーマッタに "editor.codeActionsOnSave": { "source.fixAll": "explicit", // リンターの自動修正を有効化 "source.organizeImports": "explicit" // import文の自動整理を有効化 }, } } |
このように設定することで、セーブするごとにRuffが実行され、リンターによる自動修正とインポートの自動整理が有効になります。
Pyright
VSCodeのPylanceプラグインに内包されています。(ちなみに、Pylanceは、Pythonプラグインをインストールすると自動的に追加されます。)
設定は
pyrightconfig.json
をプロジェクトルートに作成します。
1 2 3 4 | { "pythonVersion": "3.12", "typeCheckingMode": "strict" } |
typeCheckingMode
を
strict
にすることで、厳密な型チェックが行われるようになります。
pre-commit
ここまではVSCodeでの設定方法を紹介しましたが、pre-commitを使ってコミット時にチェックを強制することもできます。
まずは、pre-commitをインストールします。
1 2 3 | % rye add -d pre-commit % pre-commit install # コミット時に自動実行 |
次に、設定ファイルを作成します。プロジェクトのルートに
.pre-commit-config.yaml
を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | repos: # Ruff - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.0 hooks: - id: ruff # リンターを実行 (自動修正) args: [--fix] - id: ruff-format # フォーマッターを実行 # Pyright - repo: https://github.com/RobertCraigie/pyright-python rev: v1.1.376 hooks: - id: pyright # Pyrightを実行 |
この設定に加え、Pyrightは先ほどの
pyrightconfig.json
に以下の設定を追加する必要があります。
1 2 3 4 | { "venvPath": ".", "venv": ".venv" } |
この設定は、pre-commitがプロジェクトの仮想環境とは別の仮想環境を作り、そこでPyrightを実行するために、プロジェクトが依存しているパッケージのimportを解決することができなくなる問題への対応です。
このように
.venv
へのパスを設定することで、pre-commit経由でもPyrightを実行することができます。
さいごに
RuffはAstral.sh社が中心となって開発が進められているOSSです。Ryeのメンテナーでもあり、今まさにPython開発ツールの中心にいると思います。また、PyrightはMicrosoftが中心となっているOSSです。
現状では、Pythonの開発ツール界隈ではこの2社の存在感が大きいですね。