【Python】blackモジュールでコーディングスタイルの自動修正をする


投稿日 2023年5月15日 >> 更新日 2023年5月16日

概要

blackモジュールを使用して、コードをPEP8に準拠したフォーマットに自動修正します。

基本的な操作は、コマンドを叩いて特定のファイルに対して自動修正を実行させます。

設定ファイルにより、blackモジュールのデフォルトの振る舞いを変更します。

実行環境&使用ライブラリ

実行環境
Windows Subsystem for Linux
Python 3.10.0
使用ライブラリ ライセンス
black==23.3.0 MIT

PEP8について

blackモジュールはPEP8に準拠されたコードの自動フォーマットを行います。

PEP8とはPythonコードのスタイルガイドで、Pythonで推奨されているコーディングの規約が記されている案内です。

Pythonの構文は他のプログラミング言語に比べ非常に見やすく覚えやすい言語だと言われています。

プログラムは構文さえミスしなければ動いてしまうので、人によってはコードとコードを詰め詰めに書いたり、
その逆に、取れるだけのスペース(空白)を設けたりと様々な文体で書くことができます。

そんなソースコードのスタイルに一貫性を担保させる目的で案内されているのが「PEP8」という事になります。

詳しくはこちらを「PEP: 8」ご覧ください。

ソースコードに一貫性があれば、誰が見ても理解しやすくバグの発見や早期の問題解決にも繋がります。

blackモジュールの実装

pipでインストールします。

$ pip install black

以下の「sample.py」を使用して「black」モジュールを試して行きます。

# sample.py

from datetime import datetime
class Greeting(object):
    def __init__(self, name):
        if type(name) is not str or len(name) > 10:
            raise ValueError('名前は文字列か10文字以下でお願いします。')
        self.name = name
    def who_are_you(self):
        return self.name
    def morning_call(self):
        result = 'Good morning {}!'.format(self.name)
        return result
    def hello_call(self):
        result = 'Hello {}!'.format(self.name)
        return result
    def evening_call(self):
        result = 'Good evening {}!'.format(self.name)
        return result
    def time_now(self):
        result = datetime.now()
        return result

if __name__ == '__main__':
    greeting = Greeting('Shinji')
    print(greeting.who_are_you())
    print(greeting.morning_call())
    print(greeting.hello_call())
    print(greeting.evening_call())
    print(greeting.time_now())

blackモジュールを実行する際は、「black」コマンドの引数にコードスタイルの修正を自動で行いたいファイルやディレクトリを指定します。

※オプション無しだとコードは自動で修正(書き換え)されてしまいます。

# ファイルを指定する場合
$ black sample.py

# ディレクトリを指定する場合
$ black .
$ black dir

コードスタイルを書き換えずに修正する必要があるかどうかだけ確認したい場合は「--check」オプションを付けます。

$ black --check sample.py
would reformat sample.py

Oh no! ×××
1 file would be reformatted.

コードがどのように修正されているのか修正前と修正後の差分を表示したい場合は、「--diff」オプションを付けます。

※「--diff」オプションを付けると自動修正はされません

$ black --diff sample.py
--- sample.py   2023-05-15 02:58:48.771118 +0000
+++ sample.py   2023-05-15 02:59:32.184074 +0000
@@ -1,29 +1,36 @@
 from datetime import datetime
+
+
 class Greeting(object):
     def __init__(self, name):
         if type(name) is not str or len(name) > 10:
-            raise ValueError('名前は文字列か10文字以下でお願いします。')
+            raise ValueError("名前は文字列か10文字以下でお願いします。")
         self.name = name
+
     def who_are_you(self):
         return self.name
+
     def morning_call(self):
-        result = 'Good morning {}!'.format(self.name)
+        result = "Good morning {}!".format(self.name)
         return result
+
     def hello_call(self):
-        result = 'Hello {}!'.format(self.name)
+        result = "Hello {}!".format(self.name)
         return result
+
     def evening_call(self):
-        result = 'Good evening {}!'.format(self.name)
+        result = "Good evening {}!".format(self.name)
         return result
+
     def time_now(self):
         result = datetime.now()
         return result

-if __name__ == '__main__':
-    greeting = Greeting('Shinji')
+
+if __name__ == "__main__":
+    greeting = Greeting("Shinji")
     print(greeting.who_are_you())
     print(greeting.morning_call())
     print(greeting.hello_call())
     print(greeting.evening_call())
     print(greeting.time_now())
-
would reformat sample.py

All done! 〇〇〇
1 file would be reformatted.

上記の差分に色を付けたい場合は「--color」オプションを付けます。

$ black --diff --color sample.py
...

blackモジュールにより自動修正された「sample.py」は以下のようになります。

# sample.py

from datetime import datetime


class Greeting(object):
    def __init__(self, name):
        if type(name) is not str or len(name) > 10:
            raise ValueError("名前は文字列か10文字以下でお願いします。")
        self.name = name

    def who_are_you(self):
        return self.name

    def morning_call(self):
        result = "Good morning {}!".format(self.name)
        return result

    def hello_call(self):
        result = "Hello {}!".format(self.name)
        return result

    def evening_call(self):
        result = "Good evening {}!".format(self.name)
        return result

    def time_now(self):
        result = datetime.now()
        return result


if __name__ == "__main__":
    greeting = Greeting("Shinji")
    print(greeting.who_are_you())
    print(greeting.morning_call())
    print(greeting.hello_call())
    print(greeting.evening_call())
    print(greeting.time_now())

ディレクトリ内のファイルを複数選択して修正したい場合は「--include」オプションを使います。

※「--include」に指定する文字列は正規表現でも可

$ black dir --include dir/*.py
...

ディレクトリ内の特定のファイルを除外して修正したい場合は「--exclude」オプションを使います。

※「--exclude」に指定する文字列は正規表現でも可

$ black dir --exclude dir/*.py
...

設定ファイルで構成を変える

blackモジュールのオプション機能をコマンドラインで実行せずに、設定ファイルに定義して実行することができます。

その設定ファイルは「pyproject.toml」です。

この設定ファイルは他のサードパーティでもよく活用されており、Pythonの配布用パッケージのビルドを行うビルドバックエンドツールを設定するファイルとしても使用されています。

blackモジュールのオプション機能は「black --help」で確認できます。

PEP8で定められている1行におけるコードの長さは「79文字」となっており、blackモジュールのデフォルトでの振る舞いは「88文字」です。

この設定を変更にするには、「--line-length」オプションを定義します。

# pyproject.toml

[tool.black]
line_length = 79

ディレクトリは以下のようになります。

.
├── pyproject.toml
├── sample.py

あとは実行することで、1行の長さが「line_length」の値より大きかったらインデントで修正されます。

$ black sample.py

「--include」オプションを設定してみます。

# pyproject.toml

[tool.black]
line_length = 79
include = 'dir/sample.py'

「--include」オプションは修正したいファイルを含めたい場合に設定します。

上記では「dir」ディレクトリ内にある「sample.py」だけを修正するといった設定です。

.
├── pyproject.toml
└── dir
    ├── sample.py
    └── test_sample.py

なのでblackコマンドの引数は「dir」ディレクトリを指定する必要があります。

$ black dir --check
would reformat /mnt/c/Users/dir/sample.py

Oh no! ×××
1 file would be reformatted.

公式ドキュメント(ファイルによる設定)の方でも説明されているので参照してみてください。

それでは以上となります。

最後までご覧いただきありがとうございました。