テキストの差分を unified 形式 + 色付きで表示したいとき、diff --color -u a.txt b.txt で表示できます (Windows 環境でも Git Bash なら動作します)。
が、Python での処理中のテキスト同士の差分を表示したい場合には、subprocess.run(['diff', ...]) と外部に投げるのは面倒 / Python と連携しにくいです。
そこで、Python 標準ライブラリの difflib.unified_diff を使うと unified 形式の差分を生成できます。ただし、色は自分で付ける必要があります。colorama パッケージを使う方法と、使わない方法を記します。いずれも下図のようになります。
colorama を使って差分に色を付ける方法
colorama パッケージを利用した方法の例は以下です。最初の @ で始まる行 (差分のあるブロックの範囲を示す箇所) に遭遇したら水色にします。その後は、- で始まる行を赤に、+ で始まる行を緑にする指示も追加します (これらの指示を最初から追加すると比較するファイル名にも色が付くため)。
from colorama import Fore
def color_diff(diff):
color = {'@': Fore.CYAN + '@'}
for line in diff:
yield color.get(line[0], line[0]) + line[1:] + Fore.RESET
if line[0] == '@':
color.update({'-': Fore.RED + '-', '+': Fore.GREEN + '+'})
以下のように 2 ファイルの差分を表示できます。
from pathlib import Path
import difflib
from colorama import Fore
def color_diff(diff):
color = {'@': Fore.CYAN + '@'}
for line in diff:
yield color.get(line[0], line[0]) + line[1:] + Fore.RESET
if line[0] == '@':
color.update({'-': Fore.RED + '-', '+': Fore.GREEN + '+'})
if __name__ == '__main__':
path_a, path_b = 'a.txt', 'b.txt'
diff = difflib.unified_diff(
Path(path_a).read_text(encoding='utf8').splitlines(),
Path(path_b).read_text(encoding='utf8').splitlines(),
fromfile=path_a, tofile=path_b, lineterm='',
)
for line in color_diff(diff):
print(line)
colorama を使わないで差分に色を付ける方法
colorama を使わない方法は以下のように ANSI エスケープシーケンスを直接記述します。colorama を使う場合と挙動は同じです。colorama パッケージを使った方が可読性・可搬性のうえでよいですが、標準ライブラリで済ませたい場合はこちらの方法を使用します (私の手元の Windows 環境の Git Bash / コマンドプロンプトでもこれで動作します)。
def color_diff(diff):
ansi = {'@': '\033[36m@'}
for line in diff:
yield ansi.get(line[0], line[0]) + line[1:] + '\033[0m'
if line[0] == '@':
ansi.update({'-': '\033[31m-', '+': '\033[32m+'})
参考文献
-
tartley/colorama: Simple cross-platform colored terminal text in Python
- colorama のリポジトリで ANSI エスケープシーケンスの一覧もあります。
-
Colored diff output with Python
- 本記事は上記記事とゴール・手段が同じですが、本記事では
difflib.unified_diffが空行を返さないことを利用してcolor_diffをよりコンパクトにしています。
- 本記事は上記記事とゴール・手段が同じですが、本記事では
