Pythonによるテキストファイル比較・差分抽出
Pythonは、テキストファイルの比較や差分抽出を効率的に行うための強力なツールを提供します。これらの機能は、コードのバージョン管理、設定ファイルの変更追跡、データ整合性の確認など、様々な場面で役立ちます。
基本的な差分抽出
Pythonの標準ライブラリである`difflib`モジュールは、シーケンス(文字列やリストなど)間の差分を計算するための機能を提供します。テキストファイルの場合、ファイルの内容を行ごとに読み込み、それらをリストとして`difflib`に渡すことで差分を抽出できます。
`difflib.Differ`クラス
`difflib.Differ`クラスは、2つのシーケンスの行ごとの差分を人間が読みやすい形式で表示するのに適しています。出力は、各行がどのように変更されたかを示す記号(’+’:追加、’-‘:削除、’ ‘:変更なし、’?’:追加情報)とともに表示されます。
“`python
import difflib
def compare_files_differ(file1_path, file2_path):
“””
difflib.Differ を使用して2つのテキストファイルの内容を比較し、差分を表示します。
“””
with open(file1_path, ‘r’, encoding=’utf-8′) as file1,
open(file2_path, ‘r’, encoding=’utf-8′) as file2:
file1_lines = file1.readlines()
file2_lines = file2.readlines()
d = difflib.Differ()
diff = list(d.compare(file1_lines, file2_lines))
for line in diff:
print(line, end=”)
# 使用例:
# compare_files_differ(‘file1.txt’, ‘file2.txt’)
“`
このコードは、`file1.txt`と`file2.txt`という2つのファイルを読み込み、`difflib.Differ`を使用して差分を計算し、その結果を標準出力に表示します。
`difflib.unified_diff`関数
より一般的に使われる差分形式として、ユニファイド差分形式(Unified Diff Format)があります。これは、`diff -u`コマンドで生成される形式に似ています。`difflib.unified_diff`関数は、この形式で差分を生成します。
“`python
import difflib
def compare_files_unified(file1_path, file2_path):
“””
difflib.unified_diff を使用して2つのテキストファイルの内容を比較し、
ユニファイド差分形式で表示します。
“””
with open(file1_path, ‘r’, encoding=’utf-8′) as file1,
open(file2_path, ‘r’, encoding=’utf-8′) as file2:
file1_lines = file1.readlines()
file2_lines = file2.readlines()
# ヘッダー情報(ファイル名など)を追加
diff = difflib.unified_diff(
file1_lines,
file2_lines,
fromfile=file1_path,
tofile=file2_path,
lineterm=” # 各行の末尾に改行文字を付けないようにする
)
for line in diff:
print(line)
# 使用例:
# compare_files_unified(‘file1.txt’, ‘file2.txt’)
“`
`unified_diff`は、変更された箇所の前後のコンテキスト行も表示するため、差分の全体像を把握しやすくなります。`fromfile`および`tofile`引数で元のファイルと変更後のファイルの名前を指定すると、差分出力のヘッダーに反映されます。`lineterm=”`を指定しない場合、各行の末尾に余分な改行が追加されることがあります。
差分をファイルとして保存する
抽出した差分は、テキストファイルとして保存することができます。これは、差分を後で確認したり、他のツールに渡したりする場合に便利です。
“`python
import difflib
def save_diff_to_file(file1_path, file2_path, output_diff_path):
“””
2つのテキストファイルの内容を比較し、差分をユニファイド差分形式で
指定されたファイルに保存します。
“””
with open(file1_path, ‘r’, encoding=’utf-8′) as file1,
open(file2_path, ‘r’, encoding=’utf-8′) as file2:
file1_lines = file1.readlines()
file2_lines = file2.readlines()
diff = difflib.unified_diff(
file1_lines,
file2_lines,
fromfile=file1_path,
tofile=file2_path,
lineterm=”
)
with open(output_diff_path, ‘w’, encoding=’utf-8′) as outfile:
for line in diff:
outfile.write(line + ‘n’) # unified_diffの出力に改行を追加して書き込む
# 使用例:
# save_diff_to_file(‘file1.txt’, ‘file2.txt’, ‘diff_output.patch’)
“`
この関数は、`unified_diff`で生成された差分を`diff_output.patch`というファイルに書き込みます。`outfile.write(line + ‘n’)`としているのは、`unified_diff`の出力は各行の末尾に改行を含まないため、ファイルに保存する際に明示的に改行を追加するためです。
より高度な比較:`difflib.SequenceMatcher`
`difflib.SequenceMatcher`クラスは、2つのシーケンス間の類似度を計算したり、具体的な変更点(挿入、削除、置換)を検出したりするための、より低レベルで柔軟なインターフェースを提供します。
“`python
import difflib
def compare_files_sequencematcher(file1_path, file2_path):
“””
difflib.SequenceMatcher を使用して2つのテキストファイルの内容を比較し、
変更操作のリストを取得します。
“””
with open(file1_path, ‘r’, encoding=’utf-8′) as file1,
open(file2_path, ‘r’, encoding=’utf-8′) as file2:
file1_content = file1.read()
file2_content = file2.read()
s = difflib.SequenceMatcher(None, file1_content, file2_content)
# 類似度を計算
ratio = s.ratio()
print(f”類似度: {ratio:.2f}”)
# 変更操作(オペコード)を取得
# オペコードは (tag, i1, i2, j1, j2) のタプルで、
# tagは ‘equal’, ‘replace’, ‘delete’, ‘insert’ のいずれか
# i1, i2はシーケンス1における範囲
# j1, j2はシーケンス2における範囲
opcodes = s.get_opcodes()
print(“n変更操作:”)
for tag, i1, i2, j1, j2 in opcodes:
if tag == ‘equal’:
print(f” 一致: file1[{i1}:{i2}] == file2[{j1}:{j2}]”)
elif tag == ‘replace’:
print(f” 置換: file1[{i1}:{i2}] -> file2[{j1}:{j2}]”)
print(f” 元の内容: {file1_content[i1:i2]!r}”)
print(f” 新しい内容: {file2_content[j1:j2]!r}”)
elif tag == ‘delete’:
print(f” 削除: file1[{i1}:{i2}]”)
print(f” 削除された内容: {file1_content[i1:i2]!r}”)
elif tag == ‘insert’:
print(f” 挿入: file2[{j1}:{j2}]”)
print(f” 挿入された内容: {file2_content[j1:j2]!r}”)
# 使用例:
# compare_files_sequencematcher(‘file1.txt’, ‘file2.txt’)
“`
`SequenceMatcher`は、ファイル全体を1つの文字列として読み込み、その内容に基づいて比較を行います。`ratio()`メソッドで2つの内容の類似度を0から1の間の数値で取得できます。`get_opcodes()`メソッドは、変更をどのように適用すれば一方のシーケンスをもう一方に変換できるかを示すオペコードのリストを返します。これは、より詳細な差分解析や、差分を適用する処理を実装したい場合に有用です。
注意点と高度な利用法
* **エンコーディング:** テキストファイルを扱う際は、ファイルエンコーディング(例: UTF-8, Shift_JIS)に注意が必要です。`open()`関数で`encoding`引数を適切に指定しないと、文字化けや比較エラーが発生する可能性があります。
* **空白文字:** 行末の空白文字や、行内の連続した空白文字が差分として検出されることがあります。これらを無視したい場合は、比較前に各行からトリムするなどの前処理が必要です。
* **大文字・小文字の区別:** デフォルトでは、大文字と小文字は区別されます。区別せずに比較したい場合は、比較前に両方のテキストを小文字(または大文字)に変換する必要があります。
* **パフォーマンス:** 非常に大きなファイルを比較する場合、メモリ使用量や処理時間が問題になることがあります。その場合は、ファイル全体を一度に読み込むのではなく、チャンクごとに読み込む、あるいは`difflib`以外のライブラリ(例: `filecmp`モジュールでファイル単位の比較、あるいはより高速な差分アルゴリズムを提供する外部ライブラリ)の利用を検討します。
* **バイナリファイルの比較:** `difflib`は基本的にテキストデータ用です。バイナリファイルの比較には、`filecmp`モジュールや、`hashlib`モジュールでハッシュ値を比較するなどの方法が適しています。
まとめ
Pythonの`difflib`モジュールは、テキストファイルの比較と差分抽出において非常に強力で柔軟な機能を提供します。`Differ`クラスは人間が読みやすい差分表示に、`unified_diff`関数は標準的な差分形式の生成に、そして`SequenceMatcher`クラスはより詳細な変更操作の検出に利用できます。これらの機能を活用することで、開発プロセスやデータ管理における様々な課題を効率的に解決することができます。適切な前処理やエンコーディングの指定に注意しながら、これらのツールを使いこなすことが重要です。
