【black】Pythonのソースコードを自動整形!!コードフォーマットで議論するのはもう止めませんか?

f:id:hesma2:20210117212225p:plain

Pythonのコードを自動整形するフォーマッター、blackを紹介します。

コードフォーマットをフォーマッターに任せることで、
フォーマットではなくロジックなどに議論を集中することができます。

コードフォーマットで議論するのはもう止めませんか?

blackの特徴

最大の特徴は「設定がほとんどできない」

pypi.org

Pythonのフォーマッターとしては他にも、

  • autopep8
  • yapf

などありますが、

blackの最大の特徴はなんと言っても、設定がほとんどできないことです。

「え?それじゃあ使い勝手最悪じゃん」

と思ってしまうのも無理はありません。
僕もそう思ってましたから。

「設定がほとんどできない」の利点

設定に関して議論することや悩むことがなくなること

コードのフォーマットには正解がなく、人によって好みがかなり分かれます。

チームで開発する場合これは仕方ない問題であり、
だからこそフォーマッターを導入してコードのフォーマットを統一するのです。

■ 想像の話をします。

あなたのチームは無事フォーマッターを導入することになりました。

しかしそこで最初の壁にぶち当たるのです。

「設定どうしましょうか...??」

以前のチームや職場でformatterを使いこなしていたメンバーが奇跡的にいた場合、
そのメンバーがその時の設定をそのまま流用するかもしれません。

しかしそんなメンバーなどいない、はたまた設定に拘っているようなメンバーが複数人いてしまったら...

「設定がほとんどできない」ことによって、blackに全てを任せることができます。

blackを半年程度使ってきましたが、「このフォーマットの仕方はいけてないな」と思ったことはほとんどありません。

導入当初、同僚が

「 ' が " にフォーマットされるのだけは許せない」

と言っていましたが、

「blackがそう決めているので仕方ないですね〜」

と議論になるまでもありませんでした。blackに責任転嫁することができるからです。

しばらく使えばblackのフォーマットに慣れますよ、きっと。

blackで設定できること

じゃあ逆に何なら設定できるのか。

pyproject.toml に以下の設定を加えることができます。(詳細は後述)

  • 1行の最大の文字数
  • 対象のPythonバージョン
  • 対象ファイル
  • 対象外ファイル
VSCodeで使う場合

VSCodeで使う場合、

  • 1行の最大の文字数

の設定のみが可能です。(詳細は後述)

導入方法

次に、blackの導入方法を紹介していきます。

インストール

$ pip install black

設定ファイルを作成

プロジェクトのルートディレクトリ(一番上位の階層)に pyproject.toml を作成します。

[tool.black]
line-length = 88
target-version = ['py37']
include = '\.pyi?$'
exclude = '''

(
  /(
      \.eggs         # exclude a few common directories in the
    | \.git          # root of the project
    | \.hg
    | \.mypy_cache
    | \.tox
    | \.venv
    | _build
    | buck-out
    | build
    | dist
  )/
  | foo.py           # also separately exclude a file named foo.py in
                     # the root of the project
)
'''
1行の最大文字数
line-length = 88

88文字を超えると改行されます。
flake8を導入している場合、設定を合わせると良いでしょう。

Pythonバージョン
target-version = ['py37']

指定したバージョンに対応できる形にフォーマットされます。

対応バージョンは以下。

[py27|py33|py34|py35|py36|py37|py38]
対象ファイル
include = '\.pyi?$'

該当するファイルをフォーマットします。

正規表現で書かれているのでわかりづらいですが、.py .pyi のどちらかのファイルを対象にフォーマットを実行します。

  • \.エスケープする
  • ? は直前の文字が0, 1回出現することを示す
  • $ は末尾

.pyi はJupyterで作成したファイルに付けられる拡張子です。Jupyterを使わない場合、

include = '\.py$'

で良いと思います。

対象外ファイル
exclude = '''

(
  /(
      \.eggs         # exclude a few common directories in the
    | \.git          # root of the project
    | \.hg
    | \.mypy_cache
    | \.tox
    | \.venv
    | _build
    | buck-out
    | build
    | dist
  )/
  | foo.py           # also separately exclude a file named foo.py in
                     # the root of the project
)
'''

該当するファイルはフォーマットしません。

blackをコマンド実行する

$ black .

カレントディレクトリ(今いるディレクトリ)から下の階層をまとめてフォーマットします。

black コマンドで使える、主なオプションも紹介していきます。

[--check オプション] ルールに従っているかのチェック

blackのルールに従っているかのチェックをする(フォーマットはされません)

$ black --check .

問題なければ↓

All done! ✨ 🍰 ✨
42 files would be left unchanged.

フォーマット対象があると↓

would reformat /<省略>/black_test.py
Oh no! 💥 💔 💥
1 file would be reformatted, 42 files would be left unchanged.

エラーが発生すると↓() の閉じが足りなかったため、エラーが発生していました)

error: cannot format /<省略>/black_error.py: Cannot parse: 25:25: if __name__ == "__main__":
Oh no! 💥 💔 💥
41 files would be left unchanged, 1 file would fail to reformat.
[--diff オプション] フォーマットの差分を確認する

フォーマットの差分を確認できます。(これもフォーマットはされません)

$ black --diff .

Githubとかでおなじみに形式で出力されます。- が変更前 + が変更後を示しています。(以下は改行していたのが1行に直されてます)

--- black_test.py    2021-01-17 09:24:56.196098 +0000
+++ black_test.py 2021-01-17 09:25:00.376769 +0000
@@ -7,13 +7,11 @@
 def run():
     n = int(input())
-    tree = [
-        Node() for _ in range(n)
-    ]
+    tree = [Node() for _ in range(n)]

@@ -26,6 +24,5 @@
-
would reformat black_test.py
All done! ✨ 🍰 ✨
1 file would be reformatted, 42 files would be left unchanged.

VSCodeでファイル保存時に自動でフォーマットさせる

black コマンドの使い方を紹介しましたが、あれでは毎回コマンドを打ち込んで実行する必要があります。

VSCodeを使っているのであれば、ファイル保存時に自動でフォーマットさせるのがオススメです。

VSCodeの左下の歯車マークから設定を開くか、settings.json に以下の設定を追加します。

{
    "python.formatting.provider": "black",
    "python.formatting.blackArgs": ["--line-length", "88"],
    "editor.formatOnSave": true
}

VSCodeでは最大文字数のみ設定できるようです。

チームで開発している場合、.vscode/settings.json を作成してgit管理することを強くオススメします!

人によって設定が違うと、保存した人が変わる度に毎回同じ箇所がフォーマットされてしまうことがあります...(実体験)

pre-commitでコミット前に毎回チェックさせる

VSCode以外にも、pre-commitを使う選択肢もあります。

dev.classmethod.jp

1.pre-commitをインストール

$ pip install pre-commit

2.設定ファイル .pre-commit-config.yaml を作成

repos:
  - repo: https://github.com/ambv/black
    rev: 19.10b0
    hooks:
      - id: black
        types: [python]
        language_version: python3.8

3.pre-commitを設定

$ pre-commit install

まとめ

「設定がほとんどできない」が強みのblackを紹介しました。

  • コードのフォーマットに関して議論しなくて済む
  • 設定で議論しなくて済む
  • VSCodeやpre-commitでフォーマットを自動化できる