こんにちは。ニフティ株式会社の村山です。
先日 Python のフォーマッタである Black のアップデートを行った際にアップデート前後でフォーマット結果に差分が出たため原因を調べたときのお話です。結論から言ってしまえば、行長判定において日本語が二文字分としてカウントされるようになっていました。
別に大きく困ったわけではないですが小ネタ共有程度に。
Black 23.3.0 から Preview 機能として、行の長さの計算時に Unicode の East Asian Width を加味して計算する機能が追加されました。(リリースノート)
これまでは行の長さは len() によって計算されており、ひらがなもアルファベットも等しく1文字として扱われていました。
East Asian Width は Unicode のプロパティであり、詳細は UAX #11 に記載されていますが、かなり色々無視して大幅にざっくり言うと全角で表示されるような文字は2文字分としてカウントされるということです。
24.1.0 以降ではこれが標準の挙動になっていたみたいです。リリースノートには記載がありませんが、該当箇所を 23.12.1 と 24.1.0 で見比べると Preview かどうかで処理を分ける分岐がなくなっています。
というわけなので、ソース内で日本語を利用している行については、これまでにフォーマッタを素通りしていた行が 24.1.0 以降から突然改行されるようになることがあるみたいでした。
簡単な動作検証もしてみました。
フォーマット前:
1 2 3 4 5 6 7 |
s = "" # 89文字 s = s + "1234567890123456789012345678901234567890123456789012345678901234567890123456789" # 88文字 s = s + "123456789012345678901234567890123456789012345678901234567890123456789012345678" # 88文字(最後が全角) s = s + "123456789012345678901234567890123456789012345678901234567890123456789012345678" |
Black 23.12.1:
1 2 3 4 5 6 7 8 9 10 |
s = "" # 89文字 s = ( s + "1234567890123456789012345678901234567890123456789012345678901234567890123456789" ) # 88文字 s = s + "123456789012345678901234567890123456789012345678901234567890123456789012345678" # 88文字(最後が全角) s = s + "123456789012345678901234567890123456789012345678901234567890123456789012345678" |
Black 23.12.1(--preview
指定)、24.1.0:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
s = "" # 89文字 s = ( s + "1234567890123456789012345678901234567890123456789012345678901234567890123456789" ) # 88文字 s = s + "123456789012345678901234567890123456789012345678901234567890123456789012345678" # 88文字(最後が全角) s = ( s + "123456789012345678901234567890123456789012345678901234567890123456789012345678" ) |
デフォルトの最大行長 88 文字できっちりコードを書かれている方などは今回の変更で割とコードが読みやすくなるのではないでしょうか。私が普段扱っているプロダクトは 132 文字と長めに設定されていたので個人的にはあんまり影響がありませんでしたが…
なお、Python のリンタ兼フォーマッタとして普及してきている Ruff においても line-length の計算には East Asian Width を考慮することとなっているようです(ドキュメント)。古いバージョンの Black から Ruff に移行する場合にもこういったことが起こるかもしれません。