ただ思ったことを書き連ねています。 特に深い意味はない話です。
black と isort の微妙な違い
black は Python の著名なフォーマッタで、 isort は特に import 周りのソートを行う著名なフォーマッタです。
もちろん、役割の範囲が異なるので、そもそも違いとはなんぞやと感じる方のほうが多いと思います。 一応機能の話をしておくと、 black の機能が isort と競合する部分があるため、機能的な共存は isort 側に以下のような設定を入れることで可能になっています。
[tool.isort] profile = "black"
個人的な微妙な違いへの興味は、 --diff
オプションで得られる出力の性格にあります。
次のコードに対する、black と isort の結果の違いを見てみましょう
import typing import os print( "1" )
まずは black から。フォーマットがかからないように --check
をつけています。
$ black --check --diff tmp.py --- tmp.py 2024-02-27 16:05:01.986972+00:00 +++ tmp.py 2024-02-27 16:05:19.849258+00:00 @@ -1,6 +1,5 @@ import typing import os -print( "1" ) - +print("1") would reformat tmp.py Oh no! 💥 💔 💥 1 file would be reformatted.
次に isort です。
$ isort --check --diff tmp.py ERROR: /Users/nersonu/tmp/blog_202402/tmp.py Imports are incorrectly sorted and/or formatted. --- /Users/nersonu/tmp/blog_202402/tmp.py:before 2024-02-28 01:05:01.986972 +++ /Users/nersonu/tmp/blog_202402/tmp.py:after 2024-02-28 01:05:30.731620 @@ -1,6 +1,5 @@ +import os import typing -import os - print( "1" )
似ていますがいろいろ微妙に異なっていますね。 1つずつ見ていきましょう。
1つ目は、メッセージの位置です。 black は最後に、isort は最初に「フォーマットかけるぜ」というメッセージが入っています。 また、これは主観的な感覚ですが、 isort のメッセージのほうがなんとなくシステマティックです。
# black Oh no! 💥 💔 💥 1 file would be reformatted.
# isort ERROR: /Users/nersonu/tmp/blog_202402/tmp.py Imports are incorrectly sorted and/or formatted.
2つ目は、フォーマット対象を示すファイルパスの表記の仕方です。
black は相対パスなのに対し、isort は絶対パスになっています。
さらに、 isort にはパスのあとに :before
, :after
の文字列があります。
# black --- tmp.py +++ tmp.py
# isort --- /Users/nersonu/tmp/blog_202402/tmp.py:before +++ /Users/nersonu/tmp/blog_202402/tmp.py:after
3つ目は、時間表記です。
black は UTC なのに対し、 isort はシステムの時間表記が使われています。
ちなみに TZ
環境変数を渡したとき、 black は依然として UTC ですが、 isort は指定したタイムゾーンになります。
# black 2024-02-27 16:05:01.986972+00:00
# isort 2024-02-28 01:05:01.986972
このように、ライブラリ2つ並べて出力を見てみるだけで文化の違いを感じることができます。
ところで、これらは恐らく diff コマンドの -u
オプションから影響を受けていそうです。
$ diff -u tmp.py formatted_tmp.py --- tmp.py 2024-02-28 02:14:11 +++ formatted_tmp.py 2024-02-28 02:15:20 @@ -1,5 +1,5 @@ -import typing import os +import typing -print( "1" ) +print("1")
OS や環境依存で diff の結果が変わる可能性もあるので、一概に確定的なことはここでは言いませんが、なんとなく、 black も isort も完全に仕様として一致しているわけではなさそうです。 不思議ですね。
些細な違いで困ること?
このような些細な違いで実際に困ることはあるのでしょうか? 例えばこの diff の結果を使うような状況が……?
ところで、みなさんは Reviewdog を知っていますか?
これは、何かのライブラリやツールでコード解析をした結果等を GitHub 上 *1 で PR comment として出力できるようなツールです。
Reviewdog では、 解析結果のインプットのフォーマットとして、 diff のフォーマットに対応しています。 つまり、 black や isort の結果を使うことが出来ると考えられるわけですね。
ここで、タイムゾーン以外はほぼ diff のフォーマットの体裁を成している black は比較的簡単に使うことができます。 このノリで isort でやろうとすると痛い目を見るわけです。主にファイルパス周辺で。 実際に使うには出力を sed などで置換して消したりし、Reviewdog 側が解釈できるようにしてあげる必要があります。 あまり世で知れ渡っていないようですが、別言語でも Reviewdog でうまく扱えるように、シェルスクリプト上で出力を置換してあげるみたいなケースは他にあるようですね *2 。
black や isort に別れを告げる
ところで、ruff というツールをご存知でしょうか。
Rust 製の Python の静的リントツールですが、気づけばフォーマットも出来るようになっていました。
ruff についての説明は世に溢れているので省きますが、まだ開発途上でバグはありつつも Rust 製の非常に高速なツールです。 flake8 のような静的リントの置き換えだけでなく、black, isort のフォーマッタも ruff ですべて置き換わっていくことでしょう。
Python の開発ツールのこれから
ruff を開発している Astral 社は、同様に Rust で pip-tools の代替を目指す uv の開発も行っています。
Python の cargo を目指すというところで、 ruff を以前から採用し、 uv についても導入した rye ですが、最近作者が Astral 社にその開発を引き渡した *3 ことが話題になりました。
Rye Grows With UV :) Thanks to uv by @astral_sh Rye now installs packages much faster. https://t.co/E26gTTX3wk
— Armin Ronacher (@mitsuhiko) 2024年2月15日
フォーマッタ、静的リント、パッケージ管理ツールと、基本的な開発ツールがどんどん rye に集約されていっており、まさに cargo を目標にしていることがはっきりわかりますね。 以前紹介した PDM や、 Poetry も依存解決に installer 等を使うようになってかなり高速化しましたが、これから rye が台頭してくると、まだまだ Python の開発ツールの戦乱期は終わりを迎えそうにはありません。
そんなことをぼんやりと考えながら、わたしの有給休暇の火曜日が終わっていきました。