対象読者
- 「pythonのネイティブライブラリにdifflibがあるのは知ってるけど使い所は?」って思ってる方
- 「テキストデータの比較バッチを作ろう」と思っていてpythonも選択肢にある方
- 「pythonのネイティブライブラリいっぱいあるけどdifflibは知らないなぁ」何者か知っておくかと思う方
序章
みなさんdiffってどうやってとってますか?
- Linuxのdiffコマンド?
- WindowsやMacのネイティブアプリ?
- VSCodeなどのエディタやIDE?
だいたい上記3択かなと。僕の場合エビデンスを残さないでいい小さいファイルでは3、大きなファイルだったりエビデンスを残す場合には1で行います。(余談ですがWSL2のおかげで1がとても行いやすくなりました。)
でも今あなたが何らかのチェックバッチを作成中だとして、プログラム上から大きな文字列同士のdiffの結果を残す必要が出てきたら、この3択の中から対処しますか?
なんでPythonでdiffを取ろうと思ったの?
序章の通りプログラム上でdiffを取ることになったことによります。もう少し詳しく書くとDB上の数千、数万件のデータを比較するチェックバッチを作成することになり、完全な一致を検出するのは難しくないんですが 比較結果NGだった場合にわかりやすく差分内容を伝えたいという意図でdiffをログ上に出すことにしたのがきっかけです。
最初にやろうと思ったこと~プログラム上からdiffを取る~
pythonで気軽にdiffが取れることをしらなかった私は
比較対象である2つの情報を2つのファイルにそれぞれ書き出し、プログラムから実行環境に合わせたdiffのコマンドをコールして結果を取得しそれをログに残そうとしました。
これでも要件は当然満たされているんですが、
比較対象である2つの情報を2つのファイルにそれぞれ書き出し
これのI/Oに掛かるオーバーヘッドが気になりました。
他にも細かいところだとバッチはdocker上で起動するを前提にしてましたが「baseイメージにdiffコマンド入ってたっけ?入れるのめんどいな」とか、プログラムからOSのコマンド実行も色んな意味でイケてないし、仮にWEB系のアプリだったりするとOSのコマンドコールはインジェクションが致命的だし(今回の要件とは別ですけど)
Pythonで使えるdiffライブラリないかな?
で、ちょっと調べたらすぐにpythonネイティブライブラリにdifflibがあることがわかりました。(Python公式が見つかりました)
https://docs.python.org/ja/3.7/library/difflib.html
使い方は公式をほぼそのまま転載しちゃいますが こんな感じです。
Linuxコマンドのdiffに見慣れていれば、わかりやすいですよね。
>>> from difflib import context_diff
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py', tofile='after.py'))
*** before.py
--- after.py
***************
*** 1,4 ****
! bacon
! eggs
! ham
guido
--- 1,4 ----
! python
! eggy
! hamster
guido
若干難点なのは、generator<str>
で結果が返ってくるところです。
完全にlist<str>
で返ってきてると思って 結果をindex指定で参照してみようとしてpythonに怒られました。
とはいえ、普通そんなことしないと思うのでそれほど難点でもないですね。
まとめ
Pythonで比較バッチを作る際、「一致してないよー」を親切に教えてあげるときは difflib.context_diff
を使おう!
いつもの見慣れたLinuxのdiffコマンドっぽく比較した結果が取得できて「一致してない情報があるよ!」をログに残しやすいよ!
といったところです😄