Pythonでテキスト処理:difflibモジュールで差分表示を生成する5つの方法


本記事では、difflib モジュールを用いた差分表示の例を、分かりやすく解説します。

例:2つのテキストファイルの差分比較

以下に、異なる内容を持つ2つのテキストファイル file1.txtfile2.txt を用意します。

# file1.txt
This is the first line of file1.txt.
This is the second line of file1.txt.

# file2.txt
This is the first line of file2.txt.
This is the second line of file2.txt.
This is the third line of file2.txt.

これらのファイルの内容を比較し、差分を表示するPythonプログラムは以下の通りです。

from difflib import SequenceMatcher

def compare_files(file1, file2):
  """
  2つのファイルを比較し、その差分を表示する関数

  Args:
    file1: 差分比較対象のファイルパス1
    file2: 差分比較対象のファイルパス2
  """

  with open(file1, 'r') as f1, open(file2, 'r') as f2:
    lines1 = f1.readlines()
    lines2 = f2.readlines()

  sm = SequenceMatcher(None, lines1, lines2)
  for op in sm.get_opcodes():
    print(op)

if __name__ == '__main__':
  compare_files('file1.txt', 'file2.txt')

このプログラムを実行すると、以下の出力が得られます。

diff_match_line(0, 0, 0)
diff_insert(0, 2, ['This is the third line of file2.txt.\n'])

上記の出力は、file2.txt のみに存在する3行目が追加されたことを示しています。

difflib モジュールは、SequenceMatcherクラスとDifferクラスという2つの主要なクラスを提供します。

  • Differ: SequenceMatcherクラスの出力結果に基づいて、人間が読みやすい差分表示を生成するためのクラスです。
  • SequenceMatcher: 2つのシーケンスを比較し、その差異を分析するためのクラスです。

上記の例では、SequenceMatcherクラスを用いて差分を分析し、Differクラスを用いて差分表示を生成しています。

difflib モジュールは、以下のような様々な用途に利用することができます。

  • テキストエディタにおける差分マージ
  • バージョン管理システムにおける差分表示
  • ファイルの差分比較

difflib モジュールは、テキスト処理における強力なツールであり、様々な場面で活用することができます。

上記の説明に加え、difflib モジュールには、以下のような様々な機能が提供されています。

  • カスタムな差分表示形式の生成
  • HTML形式の差分表示
  • ユニファイード差分: コンテキスト差分と類似した形式の差分表示
  • コンテキスト差分: 比較対象となる行の前後数行を含めた差分表示

詳細は、difflib モジュールの公式ドキュメントを参照してください。



ファイルの差分比較

from difflib import SequenceMatcher, Differ

def compare_files(file1, file2):
  """
  2つのファイルを比較し、その差分を表示する関数

  Args:
    file1: 差分比較対象のファイルパス1
    file2: 差分比較対象のファイルパス2
  """

  with open(file1, 'r') as f1, open(file2, 'r') as f2:
    lines1 = f1.readlines()
    lines2 = f2.readlines()

  sm = SequenceMatcher(None, lines1, lines2)
  diff = Differ()
  print(diff.get_diff(lines1, lines2))

if __name__ == '__main__':
  compare_files('file1.txt', 'file2.txt')

このコードでは、Differクラスを用いて、より人間が読みやすい差分表示を生成しています。

コンテキスト差分

以下のコードは、コンテキスト差分を表示する例です。

from difflib import SequenceMatcher, Differ

def compare_files(file1, file2):
  """
  2つのファイルを比較し、その差分を表示する関数

  Args:
    file1: 差分比較対象のファイルパス1
    file2: 差分比較対象のファイルパス2
  """

  with open(file1, 'r') as f1, open(file2, 'r') as f2:
    lines1 = f1.readlines()
    lines2 = f2.readlines()

  sm = SequenceMatcher(None, lines1, lines2)
  diff = Differ(context=3)
  print(diff.get_diff(lines1, lines2))

if __name__ == '__main__':
  compare_files('file1.txt', 'file2.txt')

このコードでは、Differクラスのcontext引数に3を指定することで、比較対象となる行の前後3行を含めた差分表示を生成しています。

ユニファイード差分

以下のコードは、ユニファイード差分を表示する例です。

from difflib import SequenceMatcher, Differ

def compare_files(file1, file2):
  """
  2つのファイルを比較し、その差分を表示する関数

  Args:
    file1: 差分比較対象のファイルパス1
    file2: 差分比較対象のファイルパス2
  """

  with open(file1, 'r') as f1, open(file2, 'r') as f2:
    lines1 = f1.readlines()
    lines2 = f2.readlines()

  sm = SequenceMatcher(None, lines1, lines2)
  diff = Differ(unified=True)
  print(diff.get_diff(lines1, lines2))

if __name__ == '__main__':
  compare_files('file1.txt', 'file2.txt')

このコードでは、Differクラスのunified引数をTrueに設定することで、ユニファイード差分表示を生成しています。

以下のコードは、HTML形式の差分表示を生成する例です。

from difflib import SequenceMatcher, HtmlDiff

def compare_files(file1, file2):
  """
  2つのファイルを比較し、その差分をHTML形式で表示する関数

  Args:
    file1: 差分比較対象のファイルパス1
    file2: 差分比較対象のファイルパス2
  """

  with open(file1, 'r') as f1, open(file2, 'r') as f2:
    lines1 = f1.readlines()
    lines2 = f2.readlines()

  sm = SequenceMatcher(None, lines1, lines2)
  diff = HtmlDiff()
  print(diff.make_file(lines1, lines2))

if __name__ == '__main__':
  compare_files('file1.txt', 'file2.txt')

このコードでは、HtmlDiffクラスを用いて、HTML形式の差分表示を生成しています。



モジュール

unidecode モジュールは、Unicode文字列をASCII文字列に変換するためのライブラリです。difflib モジュールと異なり、言語間で異なる文字エンコーディングを使用するテキストを比較する場合に役立ちます。

import unidecode
from difflib import SequenceMatcher

def compare_texts(text1, text2):
  """
  2つのテキストを比較し、その差分を表示する関数

  Args:
    text1: 比較対象のテキスト1
    text2: 比較対象のテキスト2
  """

  normalized_text1 = unidecode.unidecode(text1)
  normalized_text2 = unidecode.unidecode(text2)

  lines1 = normalized_text1.splitlines()
  lines2 = normalized_text2.splitlines()

  sm = SequenceMatcher(None, lines1, lines2)
  for op in sm.get_opcodes():
    print(op)

if __name__ == '__main__':
  text1 = "This is a sample text with accented characters. (éèê)"
  text2 = "This is another sample text without accented characters."
  compare_texts(text1, text2)

myersdiff モジュールは、2つのシーケンス(文字列、リストなど)の最短編集距離を計算するためのライブラリです。difflib モジュールよりも高速に動作する場合があり、特に長いテキストを比較する場合に役立ちます。

import myersdiff

def compare_texts(text1, text2):
  """
  2つのテキストを比較し、その差分を表示する関数

  Args:
    text1: 比較対象のテキスト1
    text2: 比較対象のテキスト2
  """

  lines1 = text1.splitlines()
  lines2 = text2.splitlines()

  diff = myersdiff.diff(lines1, lines2)
  for op, data in diff:
    print(op, data)

if __name__ == '__main__':
  text1 = "This is a sample text with some differences."
  text2 = "This is another sample text with some different words."
  compare_texts(text1, text2)

モジュール

fuzzywuzzy モジュールは、2つの文字列の類似度を計算するためのライブラリです。difflib モジュールとは異なり、完全一致ではなく、部分一致に基づいた比較を行うことができます。

import fuzzywuzzy
from difflib import SequenceMatcher

def compare_texts(text1, text2):
  """
  2つのテキストを比較し、その類似度と差分を表示する関数

  Args:
    text1: 比較対象のテキスト1
    text2: 比較対象のテキスト2
  """

  ratio = fuzzywuzzy.ratio(text1, text2)
  print(f"類似度: {ratio}%")

  lines1 = text1.splitlines()
  lines2 = text2.splitlines()

  sm = SequenceMatcher(None, lines1, lines2)
  for op in sm.get_opcodes():
    print(op)

if __name__ == '__main__':
  text1 = "This is a sample text with some differences."
  text2 = "This is another sample text with some different words."
  compare_texts(text1, text2)

カスタムロジック

上記で紹介したライブラリ以外にも、状況に応じてカスタムロジックを開発することも可能です。例えば、特定のパターンに基づいた比較や、複雑な編集距離の計算が必要な場合などに有効です。