Pythonのフォーマッタをblackに移行してみたので色々所感をまとめてみる

最近以下の個人開発のPythonライブラリでフォーマッタをautopep8からblackに移行したので雑多に所感などをまとめておきます。

https://github.com/simon-ritchie/apysc

# blackとは

- Python界隈でこの記事執筆時点で一番スターの付いているPythonのフォーマッタライブラリです（執筆時点でGitHubのスターが約3万）。
  - Pandasやscikit-learnなどのPython界隈の有名ライブラリ含め、非常に多くのリポジトリで使用されています（記事執筆時点でGitHub上のUsed byの表示が約17000リポジトリ）。
- 色を混ぜて別の色にできない黒色のごとく、基本的にスタイルの調整が効きにくくなっているのと調整せずに使うのが基本となっているようです。
  - 融通が効かない代わりにプロジェクトごとにスタイルが異なっていたりといったことを避けられたり、最初のスタイルを決めたりする手間を省くことができます。つまり各プロジェクトでスタイルが統一されるためblackに慣れていればblack採用プロジェクトでは違和感の少ないスタイルでコードを触ることができます。

GitHubリポジトリ:

https://github.com/psf/black

# 何故autopep8を使っていたのか

元々昔から続いているお仕事でのPythonプロジェクトではLint導入当時はblackなどは有名ではなかったため昔からあったautopep8を導入して長らく使っていました。

その後しばらくしてからスタートした個人開発のライブラリでも仕事と合わせた方が色々日々のスイッチングコストが低くて良いだろう・・・ということでプライベートの方でもautopep8を採用していました（blackを使ったことがなかったので躊躇していたというのもあります）。

それ以来結構コード量も多かったので移行が面倒でそのままにしてしまっていました。

# 何故移行しようと考えたのか

- 世の中のPython界隈が大体blackになってきたので長い物には巻かれろ的にマジョリティに合わせておきたかったというのがあります。
- また、別途公開していたPython関係のLintライブラリに対して「blackのフォーマットだとうまく動作しないケースがあるから直したよ」とプルリクをいただいたのがトドメになりました。流石に移行して雰囲気を知っておかないとまずいなと感じました。

# blackに移行してみて良かったと感じた点

## プロジェクトのコーディングスタイルの資料を追加しなくともOKになった

Python界隈の方なら「コーディングスタイルはblackのデフォルト設定ね」でOKでしょうし、自前でコーディングスタイルを決めたり資料を整備する手間が省けるのは良いと思いました。

## 他の方のコーディングスタイルのキャッチアップが楽になるのではという気がしている

blackのスタイルで統一されていれば他のblack採用プロジェクト経験者の方ならコーディングスタイルのキャッチアップが楽なのではという感じがしています。逆も然りでこのライブラリで作業していた方が他のblack採用プロジェクトで作業する際にもスイッチングコストなどが発生しにくくて良いのではと思われます。

ただ、この辺はまだ移行したばかりなので年単位で運用してみてもう少し詳しく判断したいという所ではあります。

## デフォルトで並列実行してくれる

Lint反映時間などはなるべく短くなった方が開発体験が良くなると思いますが、blackではデフォルト（ビルトイン）で並列実行してくれるため処理時間を短くしてくれます。自前で色々スクリプトを書いて並列化などしなくても済むので楽です。

## デフォルトで更新されていないモジュールをスキップしてくれる

こちらもLintの時間短縮に役立つ機能となりますが、更新されていないモジュールに対する実行をスキップして処理時間を短縮してくれます。自前で色々ハッシュ値判定などやらなくて済むので楽で良いです。

# blackの採用で少々気になった点

大体はblackのスタイル反映結果は許容範囲だったのですが、一部の挙動が最初少し気になったのでその辺についても触れておきます。ただ、気になったのは最初だけで些細な内容ですし統一感のために許容できる範囲でもあります。

## 文字列の改行が無くなり、且つクオーテーションの連結はされないケースがある

たとえば以下のような（辞書内などで使われている形で）文字列の定義があったとします。
文字数が多いので改行が入っていたとします。Pythonだと文字列のクオーテーション間は+記号などが無くとも連結されるのでこれは1つの文字列として扱われます。

```py
{
    "Lorem ipsum": "dolor sit amet, consectetur adipis sed eiusmod "
    "tempor incididunt"
}
```

こういったコードに対してblackを反映すると88文字以内であれば改行が削除されます。また、各クオーテーションは残ったままになります。以下のような記述に変換されます。

```py
{
    "Lorem ipsum": "dolor sit amet, consectetur adipis sed eiusmod " "tempor incididunt"
}
```

この点はクオーテーションが一緒なら統合してくれても良かった・・・という気も少しします（クオーテーションが分かれている意味が無いケースが発生します）。まあでもシングルクオーテーションだったりf-stringsやraw-stringsなど色々条件があるのでコードに悪影響を与えない・・・と考えると連結しない方が無難・・・という面もあるかもしれませんね。

# 採用で工夫した点など

## doctestのコードに関してはサードパーティーのライブラリを追加した

通常のコードの他にもdoctestなどもdocstring内に色々記述していました。

※doctestに関しては以前記事にしているので必要な方はそちらをご確認ください。

https://qiita.com/simonritchie/items/124708cfb7a36b857d92

通常のblackライブラリではdoctestのコードまではフォーマットしてくれません。ただしその辺りが統一されていないと気になる・・・ため調べていたらサードパーティーのライブラリがあったためそちらを利用させていただきました。

https://github.com/keewis/blackdoc

こちらも通常のblack同様に更新されていないモジュールをスキップしてくれたりの挙動は一緒です。動作も想定した通りに動いてくれてdoctestもblackのスタイルで統一することができました。作者の方ありがとうございます:bow:

## ドキュメントのコードブロックへの反映に関して

ドキュメントはSphinx + MySTを使用してマークダウンで書いています。

※Sphinxなどに関しては以前記事で触れたのでそちらをご参照ください。

https://qiita.com/simonritchie/items/3a5cd8fa622ba2bacffe

https://qiita.com/simonritchie/items/eef7d50959ad9bdb8440

ドキュメント内にもPythonのコードブロックがあるわけですが、そちらもdoctestなどと同様に通常のコードとスタイルがずれていると気になってしまいます。また、ドキュメントのコードブロックは人の目に付きやすいところでもあるためblackの一般的なスタイルで統一したいと感じました。

この辺りは以前自前でflake8を各コードブロックのコードに反映するスクリプトは書いていたので同じような仕組みで今回blackが反映されるようにしました。

探せばもしかしたらサードパーティーのライブラリなどがあるかもしれませんがざっと探した感じ無さそうな気がしたのと自前で書いても大した負担ではなかったので自前で対応しています。

## isortにはblack用のオプションがあるので併用する場合にはそちらを設定すると楽

isortも以前から使っていたのですが、何も考えずにblackを追加すると処理が競合します。

その辺の設定に関してはisort用の設定ファイルに以下の記述を追加するか、もしくはisortのコマンドで`--profile black`と引数を加えてあげると競合しなくなり設定が楽です。

```
[isort]
profile=black
```

参考 :

https://zenn.dev/choimake/articles/44475b37e8986a6d28c3

## flake8と併用する場合には1行の文字数を調整する必要がある

flake8のデフォルトの1行の文字数は79文字くらいになっている一方でblackでは88文字となっています。

blackでは88文字以内になるなら余分な改行などを消して1行に入れる挙動をするため、その辺でflake8と併用する場合は競合します。

そのためflake8側の設定を調整したりコマンドの引数に`--max-line-length 88`といった指定を追加する必要があります。

## わざとシングルクオーテーションのコードに対して書いているテストも変換される

通常の実装はダブルクオーテーションにblackで統一されるものの、Lint周りの制御などでシングルクオーテーションのパターンでも動作するようにテスト側でわざとシングルクオーテーションを使う・・・みたいな特殊なケースでも強制的にダブルクオーテーションに変換されるというケースが発生し、ごくごく稀ですがシングルクオーテーションに利用の意図を設ける場合に競合する箇所がありました。

## 今までシングルクオーテーションをメインで使っていたので慣れるまで違和感が出続けそうな感じがある

今までは仕事でもプライベートでもPythonではシングルクオーテーションをメインに使っていました。しかしblackでは基本的にはダブルクオーテーションがメインになります。この辺は慣れるまで違和感が発生しそうです。

また、仕事ではまだblackに移行していないためそのうち移行しないと仕事とプライベートでの切り替え面があまり良くないことになりそうです。この辺は仕事側が長期プロジェクトなので結構コード量が多いところではありますが他のタスクが落ち着いてきたら折を見て移行したいところです。

# 参考資料・参考サイト

https://github.com/psf/black

https://github.com/keewis/blackdoc

https://qiita.com/simonritchie/items/124708cfb7a36b857d92

https://qiita.com/simonritchie/items/3a5cd8fa622ba2bacffe

https://qiita.com/simonritchie/items/eef7d50959ad9bdb8440

https://zenn.dev/choimake/articles/44475b37e8986a6d28c3