【Python】コミット差分のみblackで整形する 【darker】
前書き
コードの整形はフォーマッタに任せたいものです。
理想的には、全員が同じスタイルでコードを整形できるようにpre-commitなどを利用してコミット時にフォーマッタを自動実行します。
しかしプロジェクトの途中参加など、導入が難しいケースもあると思います。
今回の自分は、プロジェクトにフォーマッタが導入がされておらず
- せめて自分のコミット分だけはblackで整形したい。
- 共通ライブラリを更新時、ファイル単位ではなく行単位で整形したい。
という状況でした。 根本解決を諦め、次善の方法はないかと調べたところ darkerという、black(とisort)のwrapperライブラリが良さそうでした。
(なお、Darker作者はGitHubのREADMEに、本家のblackにも行単位のフォーマット機能は将来導入されそうだと言及しています.)
darkerについては日本語の情報が少ないようだったので、試してみた内容をまとめておこうと思います。
参考リンク
環境
バージョン | |
---|---|
MacOS Big Sur | 11.6 |
Python3 | 3.10.2 |
darker | 1.3.2 |
black | 21.12b0 |
Pygments | 2.11.2 |
darkerのインストール
インストール
仮想環境を作ってpip installします。
$ python3.10 -m venv env $ source env/bin/activate
(env)$ pip install darker
注意: blackのバージョンを下げる必要がある.(2022-02-05) 修正されています.
(2022-03-04)追記: 現在は修正されています。この項目は不要ですが、記録として残しておきます。
2022-02-05 現在、このままだとdarkerを利用できません。
darkerはblackのwrapperなので、darkerをインストールすると最新のblackが一緒に落とされます。しかし、最新のblack(ついにβが取れた22.1.0
)はdef find_project_root
関数の返り値の型がPathからtupleに変わってしまい、darerは未対応です。
こちらのプルリクで、既にdarkerの作者(akaiholaさん)が修正中のようです。 追記: -> すでに修正されています。
とりあえずは、blackのバージョンをβ版まで落とせば良いです。
(余談ですが、以下のようにして実行するとinstall可能バージョンを見ることができて便利です.(多分正当な方法ではないですが...))
(env)$ pip install black== ............................(省略)........................... 20.8b1, 21.4b0, 21.4b1, 21.4b2, 21.5b0, 21.5b1, 21.5b2, 21.6b0, 21.7b0, 21.8b0, 21.9b0, 21.10b0, 21.11b0, 21.11b1, 21.12b0, 22.1.0) ERROR: No matching distribution found for black==
最新の一個前は21.12b0
ですので、こちらにダウングレードしておきます。
(env)$ pip install black==21.12b0
Pygmentsで色をつける
もうひと手間加えて、出力結果の見た目をきれいにしておきます。
Pygments を同じ環境にインストールしてあると、darkerは出力結果をカラーにしてくれます。
(env)$ pip install Pygments==2.11.2
特に設定は必要ありません。
darkerの出力結果は、元々このような見た目ですが、
このように変わります。
使い方
新規差分を整形
まず、darkerはgit diff
を利用するので、ディレクトリをgit 管理下におく必要があります。
適当なディレクトリを作成し、最初のコミットまで済ませておきます。(内容はなんでもOKです.)
$ git init $ touch README.md $ git add README.md $ git commit -m "first"
これでHEADができたので、darkerを利用できます。以下のようなpythonファイル(darker_test.py
)を作成します。
(とにかく横に長くしたかっただけなので、適当です)
def format_name_and_age_to_profile(name: str | None, age: int | None, address: str | None): return f"{name} -- {age} -- {address}"
一旦、ここでコミットします. darkerが「コミット差分」に効くことを検証したいからです。
$ git add darker_test.py $ git commit -m "一つ目の関数" $ git log --oneline 7e25910 (HEAD -> master) 一つ目の関数 b6bee90 first
では、darker_test.pyにもう一つ記述を加えて以下のようにします。
def format_name_and_age_to_profile(name: str | None, age: int | None, address: str | None): return f"{name} -- {age} -- {address}" def format_name_and_age_to_profile_version_2(name: str | None, age: int | None, address: str | None): return f"{name} -- {age} -- {address}" # 2回目のコミット
まだコミットはしないでください! (addまではOKです.)
ここで darkerで修正差分を出力してみます.
今回は以下のように、カレントディレクトリ(.
)かファイルを直接指定します。どちらでも結果は変わりません.
$ darker --diff . # カレントディレクトリ $ darker --diff darker_test.py # ファイル指定.
今回の変更分だけが整形されていることがわかります。 最初にコミット済みの、一つ目の関数は整形されていません。
また、この時点では元ファイルは変更されていません。修正をファイルに反映したければ、--diff
オプションを外します。
$ darker .
整形後は以下のようになります。二つ目の関数(まだコミットしていない分)だけが、整形されています。
def format_name_and_age_to_profile(name: str | None, age: int | None, address: str | None): return f"{name} -- {age} -- {address}" def format_name_and_age_to_profile_version_2( name: str | None, age: int | None, address: str | None ): return f"{name} -- {age} -- {address}" # 2回目のコミット
では一旦コミットしておきましょう。
$ git add . $ git commit -m "二つ目の関数(整形済み)"
コミットを指定して整形
darkerはgit-diffを利用しているので、例えばあるコミットからあるコミットまでという範囲を指定し、その時の変更をターゲットに整形が可能です。
先程のコミットログは以下のようになっています。
$ git log --oneline 8de1f64 (HEAD -> master) 二つ目の関数(整形済み) 7e25910 一つ目の関数 b6bee90 first
一つ目の関数は整形できていなかったので、こちらを指定して整形を行います。firstコミット(b6bee90)から7e25910の間の変更なので以下のように指定します。
最後にPATHを指定する必要があるのですが、直接ファイル名(darker_test.py
)を指定するか、以下のようにワイルドカードを使う必要がありました。
.
(カレントディレクトリ)指定は、なぜかできない仕様のようでした。
$ darker --diff --revision b6bee90..7e25910 * # *.pyやdarker_test.pyでも大丈夫.
またhelpを見ると、コミット間は...(ドット三つ)で区切るように書いてありました。(2つでもできますが、違いは不明です)
結果は、指定したコミット間で記述した一つ目の関数が整形されています。
pre-commitでdarkerを使う
まずpre-commitの導入です。
(env)$ pip install pre-commit (env)$ pre-commit --version pre-commit 2.17.0
.pre-commit-config.yaml
を Darkerの公式GitHubにある参考例を元に記述します。
blackのバージョンを落としてください (2022-02-06現在. 理由は前述の通り.)
repos: - repo: https://github.com/akaihola/darker rev: 1.3.2 hooks: - id: darker additional_dependencies: [black==21.12b0] # 最新blackだと失敗する.
$ pre-commit install
ではdarkerにまた長い名前の関数を1行で書き、実行してみます。
こちらを追記し...
def format_name_and_age_to_profile3(name: str | None, age: int | None, address: str | None): return f"{name} -- {age} -- {address}"
(env)$ git add . (env)$ git commit
結果、コミットしていない差分のみがフォーマットされているはずです。
そもそも自分だけコード整形することに意味はあるか?
同僚の方にも相談させていただいたのですが、
- そもそも自動フォーマットはレビューの負担軽減のためにやっている.
- チームでコードを統一することが目的.
という指摘をいただきました。
全くその通りで、本当はプロジェクト全体で設定ファイルを共有し、pre-commitでblack/isort/flake8/mypyあたりをかけることが自動フォーマットの目的に即していると思います。
自分の場合、
- 既存のpythonコードはフォーマッタなどを適応していない。また、クオートなどのスタイルはバラバラ。
- 新規にコードを追加するのは基本的に自分だけ。レビューは受ける.
- 既存のpythonスクリプトも、自前のutility関数が大量に入ったライブラリもそこそこの量がある。
という状況でした。
そのため、自分自身が書いた範囲のコードを読みやすくしたく、書くときには余計なことを考えなくて済むようにやはりフォーマッタは欲しいと思いました。そのため、本来の目的とは少し離れてしまうことを念頭に置き、一時しのぎ的にdarkerを利用しようと思っています。
もちろん、これは根本解決にはならない、ということを常に忘れないようにしたいと思います。
まとめ
- 可能ならプロジェクト立ち上げ時にpre-commitを設定しておいた方がよい。
- 自動フォーマットの目的は何か見失わないようにする。
- それでもコミット差分だけをフォーマットしたいなら、darkerは選択肢に入ってくる。
ご意見、ご指摘などいただけると嬉しいです:pray: