📝

Pythonのリンター・フォーマッターをしっかりと理解する(Flake8, Black, isort, mypy)

2022/10/30に公開約7,000字

この記事は何?

Pythonでよく使われるリンター・フォーマッターについて「しっかり」理解するために整理した情報をまとめました。

📋書いてあること

  • リンターとフォーマッターの違いと役割
  • Pythonで用いられるリンター・フォーマッターの概要と使い方
  • VS Codeでの利用方法

🧑‍💻対象としたリンター・フォーマッター

ツール名 種別 概要
Flake8 リンター 論理エラー・PEP8スタイルなどのチェック
Black フォーマッター PEP8に従ったスタイルフォーマット
isort フォーマッター モジュールのインポート順のフォーマット
mypy リンター 型ヒントに基づいた型チェック

Pythonの環境構築の方法として、これらのツールの導入する方法はよく見かけます。しかし、これらのツールがどのような機能を持っているのか、なぜ複数のツールを併用するのかはよく理解せずに利用していました。

今回は、なるべく公式ドキュメントなどを参考にしつつ、各ツールの特徴や役割についてまとめてみました。「他の記事を参考にしてツールを導入したものの、どんな意味があるかわかっていない」「どのツールがどう動いているかわからない」といった方の理解が深まると嬉しいです。

リンター・フォーマッターとは

まずはじめに、リンターとフォーマッターの違いについて整理しておきます。

リンター(linter) とは、ソースコードを静的に(つまり、動的に実行する前に)解析して、エラーなどをチェックしてくれるツールです。IDEでソースコードを書いているときに、赤色の波線で指摘してくれるアレのことですね。ただし、リンターはエラーを指摘するだけで、修正は行いません。

フォーマッターは、ソースコードのスタイルを自動的に修正するツールです。これを利用することで、チームで開発する際にコーディングスタイルを統一するのに役立ちます。

プログラミングの際は、各プログラミング言語に対応したリンター・フォーマッターを組み合わせてコーディングしていきます。

Pythonのリンター・フォーマッター

ここからは、Pythonのリンター・フォーマッターを紹介します。用途に応じて数多くのツールが使われているようですが、ここでは代表的な4つのツールを紹介します。

Flake8|論理エラーやスタイルをチェックする

Flake8|公式ドキュメント

Flake8は、Pythonソースコードの論理エラーやスタイルをチェックするリンターです。以下の3つのツールのラッパーになっており、flake8コマンドを実行するだけで色々なチェックをまとめて実行できます。

ツール名 概要 ドキュメント
Pyflakes 論理エラーのチェック https://pypi.org/project/pyflakes/
pycodestyle PEP8スタイルチェック https://pycodestyle.pycqa.org/en/latest/
maccabe 複雑度のチェック https://pypi.org/project/mccabe/

ちなみに、maccabeではソースコードの複雑度(ネストの深さなど)をチェックしてくれるようです。デフォルトではOFFになっているため、この記事では説明を割愛します。

Pythonのコーディング規約について

Pythonでは、PEP8という標準のコーディング規約が定められています。pycodestyleでは、ソースコードがこの規約に従っているかをチェックしてくれます。PEP8をもしご存知でない場合は、公式ドキュメント一通り読んでおくことをオススメします。

Flake8を実行する

ここでは試しに、以下のようなソースコードに対してFlake8を実行してみます。

import os


def fizzbuzz(n: int) -> str:
    if n % 15 == 0:
        return "FizzBuzz"
    elif n % 3 == 0:
        return "Fizz"
    elif n % 5 == 0:
        return "Buzz"
    else:
        return str(n)



if __name__ == "__main__":
    print(fizzbuzz( 1 ))

以下のように、無駄なモジュールをインポートしている箇所と、PEP8の規約違反が検出されました。

❯ flake8 fizzbuzz.py
fizzbuzz.py:1:1: F401 'os' imported but unused
fizzbuzz.py:16:1: E303 too many blank lines (3)
fizzbuzz.py:17:20: E201 whitespace after '('
fizzbuzz.py:17:22: E202 whitespace before ')'

なお、通常はIDE上でインタラクティブにエラーを確認しますが、その方法は後述します。

Black|PEP8に従ってフォーマットする

Black|公式ドキュメント

Flake8による解析で、PEP8の規約違反をチェックできますが、自動的には修正してくれません。これを自動的に修正するツールがフォーマッターです。コーディングスタイルには色々な流儀があるので、Pythonのフォーマッターも色々な種類があります。ここではその中でも、よく使われているBlackを紹介します。

Blackは他のフォーマッターに比べて制限がきついことが特徴です。例えばPEP8では、単一引用符'と二重引用符"はどちらを使っても良いことになっていますが、Blackでは二重引用符"に統一されます。他にも改行位置や改行の方法なども、統一された書き方に強制的に変換されます。

自分の好みと異なるフォーマットに変換されることもあったので、使い始めたときは少し窮屈でした。しかし個人の好みが入る余地がないため、チームで開発するときはこちらの方が良いのだと思います。

Blackを実行する

では、先ほどのソースコードにBlackを適用してみます。(--diffオプションで差分を表示できます。)

❯  black fizzbuzz.py --diff
--- fizzbuzz.py 2022-10-30 09:10:34.833491 +0000
+++ fizzbuzz.py 2022-10-30 09:16:27.672099 +0000
@@ -10,8 +10,7 @@
         return "Buzz"
     else:
         return str(n)
 
 
-
 if __name__ == "__main__":
-    print(fizzbuzz( 1 ))
+    print(fizzbuzz(1))
would reformat fizzbuzz.py

All done! ✨ 🍰 ✨
1 file would be reformatted.

フォーマットが自動的に修正され、余分な空行や空白が削除されました。なお、Blackで修正されるのはフォーマットだけです。論理エラー(Pyflakesが指摘する箇所)は自分で修正します。

Flake8との併用

Flake8とBlackを同時に利用すると、両者の制約の違いにより競合が発生します。例えば、Flake8では1行の最大文字数を79文字に規定している一方で、Blackでは88文字まで許容しています。

競合を解決するためには、以下のような内容を設定ファイルに記載して、Flake8側の設定を変更しておきます。

.flake8
max-line-length = 88
extend-ignore = E203

詳細は公式ドキュメントを参照してください。

isort|モジュールのインポート順を修正する

isort|公式ドキュメント

isortはモジュールのインポート順を修正するツールです。Blackではインポート順を修正してくれないため、フォーマッターとしてこのツールを併用します。

PEP8のimportの項目では、モジュールのインポート順が以下のように規定されています。

import文 は常にファイルの先頭、つまり モジュールコメントや docstring の直後、そしてモジュールのグローバル変数や定数定義の前に置くようにします。

import文 は次の順番でグループ化すべきです:

1.標準ライブラリ
2.サードパーティに関連するもの
3.ローカルな アプリケーション/ライブラリ に特有のもの

上のグループそれぞれの間には、1行空白を置くべきです。

これらはソースコードの可読性にはあまり影響しないかもしれません。しかし、この通りに記載した方が確かにわかりやすいですし、チームで開発する際には統一されていた方が良いですね。

isortを実行する

例として、以下のような順番でモジュールをインポートしたソースコードを、整形してみます。

from . import my_sample_package
import numpy
import os, sys, json

これをisortコマンドで修正します。

❯ isort fizzbuzz.py

以下のように、PEP8の規約に従った形式に変更されました。同一グループ状ではアルファベット順に並びます。これより、誰が書いても同じ順序で記載されるようになりますね。

import json
import os
import sys

import numpy

from . import my_sample_package

Blackとの併用

isortもBlackと競合する部分があるので、以下のような設定ファイルを作成して、isort側の設定を変更します。

pyproject.toml
[tool.isort]
profile = "black"

こちらも詳細は公式ドキュメントを参照してください。

mypy|型チェックを行う

mypy|公式ドキュメント

Python3.5以降では型ヒントがサポートされています。mypyでは、型ヒントに基づいた型チェックを行うことができます。

最初のサンプルコードを少し修正して、int型の変数を要求するfizzbuzz()関数に、str型の変数を代入してみます。このまま実行するとエラーになってしまいます。

def fizzbuzz(n: int) -> str:

...省略...

if __name__ == "__main__":
    s = input()
    print(fizzbuzz(s))

これをmypyでチェックすると、型エラーが出力されました。プログラムの実行前にエラーに気づくことができ、効率的です。

❯ mypy fizzbuzz.py
fizzbuzz.py:14: error: Argument 1 to "fizzbuzz" has incompatible type "str"; expected "int"
Found 1 error in 1 file (checked 1 source file)

ちなみに私自身、個人でPythonコードを書くときは型ヒントを使ったことがありませんでした。実行時に初めて型エラーに気づいてイライラすることがよくあったので、今後はできるだけ型ヒントを書いておこうと思います。

VS Codeでリンター・フォーマッターを利用する

ここまで紹介したツールを実行することで、ソースコードのエラーを未然に検知し、なおかつフォーマットを統一することができます。

ソースコードの記載中にもエラーの検知とフォーマットが実行されると、より便利です。ここではVS Codeで自動的にこれらのツールを実行する手順を紹介します。
(VS Codeと拡張機能「Python」がインストールされていることを前提にしています。)

VS Codeの設定画面かsetttings.jsonを開いて、以下の設定を追記してください。Flake8とmypyでのチェックと、Blackとisortでのフォーマットが自動で実行されるようになります。

settings.json
{
  "editor.formatOnType": true,
  "python.linting.flake8Enabled": true,
  "python.linting.mypyEnabled": true,
  "python.formatting.provider": "black",
  "editor.codeActionsOnSave": {
    "source.organizeImports": true
  }
}

isortの設定方法については以下の記事を参考にさせていただきました。
https://zenn.dev/wtkn25/articles/python-isort#(vscodeの設定)設定のcode-actions-on-save開く

この状態でコードを記載すると、Flake8とmypyでのチェックが自動で実行され、エラーが表示されます。
VS Codeの記載例
また、ファイルの保存時に、Blackとisortでのフォーマットが自動で実行されるようになります。

まとめ

Pythonでよく利用されるリンター・フォーマッターについて情報を整理してみました。今までは、理解が曖昧なままで利用してきましたが、各ツールの公式ドキュメントも参照したことで、それぞれの役割を明確に理解することができました。

これらのツールは個人の開発でも便利なものですが、チーム開発で特に効果を発揮します。そういったケースにおける仮想環境の構築方法や、その中でこれらのツールを実行する方法についても、別途整理してみようと思います。

参考文献

https://zenn.dev/yhay81/articles/yhay81-202102-pythonlint

Discussion

ログインするとコメントできます