【Python】diffを使ってテキストやファイルを比較する方法を初心者向けに解説
こんにちは、とまだです。
「このコード、前と何が変わったんだっけ?」
プログラミング学習をしていると、こんな疑問を感じることってありませんか?
特に複数のバージョンを管理したり、他の人とコードを共有したりするときに、変更箇所を素早く把握したくなりますよね。
今回は現役のエンジニア、そして元プログラミングスクール講師としての経験から、Pythonで差分(diff)を取る方法について解説します。
diffとは何か
diffとは、2つのテキストやファイルを比較して、その差分を表示する仕組みです。
日常生活に例えると、書類の改訂版と元版を並べて「ここが変更されました」と赤ペンでチェックするようなものですね。
プログラミングでは、このような差分確認を自動化できます。
Pythonにはdifflibという標準ライブラリがあり、これを使えば簡単に差分を取得できるんです。
基本的な使い方:テキストの比較
まずは、シンプルなテキスト同士の比較から始めてみましょう。
以下のコードは、2つの文字列の差分を表示する基本的な例です。
import difflib
# 比較する2つのテキスト
text1 = """Python is fun.
I like programming.
Let's learn together."""
text2 = """Python is awesome.
I love programming.
Let's learn together."""
# 行ごとに分割
lines1 = text1.splitlines()
lines2 = text2.splitlines()
# 差分を取得
diff = difflib.unified_diff(lines1, lines2, lineterm='')
# 結果を表示
for line in diff:
print(line)
このコードでは、unified_diff()
という関数を使っています。
実行すると、変更された行の前に-
や+
の記号が付いて表示されます。
-
は削除された行、+
は追加された行を意味します。
ファイル同士の比較
実際の開発では、ファイル同士を比較することが多いですよね。
ファイルの比較も、基本的な流れは同じです。
import difflib
# ファイルを読み込む関数
def read_file(filename):
with open(filename, 'r', encoding='utf-8') as f:
return f.readlines()
# ファイルを読み込み
file1_lines = read_file('version1.py')
file2_lines = read_file('version2.py')
# 差分を取得
diff = difflib.unified_diff(
file1_lines,
file2_lines,
fromfile='version1.py',
tofile='version2.py'
)
# 差分を表示
for line in diff:
print(line, end='')
fromfile
とtofile
を指定すると、どのファイル間の差分なのかが分かりやすくなります。
GitHubでプルリクエストを見たときのような表示になるイメージです。
より視覚的な比較:HTMLで差分表示
コンソールでの表示だけでなく、HTMLで見やすく表示することもできます。
これは、非エンジニアの方にも共有しやすい方法です。
import difflib
text1 = ["りんご", "みかん", "ぶどう"]
text2 = ["りんご", "バナナ", "ぶどう", "メロン"]
# HTML形式で差分を生成
html_diff = difflib.HtmlDiff()
result = html_diff.make_file(text1, text2)
# HTMLファイルとして保存
with open('diff_result.html', 'w', encoding='utf-8') as f:
f.write(result)
print("diff_result.htmlを開いて確認してください")
生成されたHTMLファイルをブラウザで開くと、左右に並んだ形で差分が色付きで表示されます。
変更箇所が一目で分かるので、レビューや確認作業に便利です。
実践的な活用例
設定ファイルの変更確認
アプリケーションの設定ファイルが更新されたとき、どこが変わったのか確認したい場合があります。
import difflib
import json
# JSON設定ファイルを比較する例
def compare_json_configs(file1, file2):
with open(file1, 'r') as f1:
config1 = json.dumps(json.load(f1), indent=2).splitlines()
with open(file2, 'r') as f2:
config2 = json.dumps(json.load(f2), indent=2).splitlines()
diff = difflib.unified_diff(
config1,
config2,
fromfile='旧設定',
tofile='新設定'
)
return list(diff)
# 使用例
differences = compare_json_configs('config_old.json', 'config_new.json')
for line in differences:
print(line)
JSONファイルを整形してから比較することで、構造的な変更も分かりやすくなります。
ログファイルの差分チェック
システムログの変化を追跡したい場合にも使えます。
import difflib
from datetime import datetime
def compare_logs(yesterday_log, today_log):
# 最新の100行だけを比較(大きなファイル対策)
with open(yesterday_log, 'r') as f:
yesterday_lines = f.readlines()[-100:]
with open(today_log, 'r') as f:
today_lines = f.readlines()[-100:]
# 新しく追加された行だけを抽出
diff = difflib.unified_diff(
yesterday_lines,
today_lines,
n=0 # コンテキスト行を表示しない
)
new_lines = [line for line in diff if line.startswith('+') and not line.startswith('+++')]
return new_lines
# エラーログの新規発生を確認
new_errors = compare_logs('error_2024-01-17.log', 'error_2024-01-18.log')
print(f"新規エラー: {len(new_errors)}件")
注意点とコツ
大きなファイルを扱うとき
メモリに全内容を読み込むため、巨大なファイルでは注意が必要です。
必要に応じて、ファイルを分割して処理することを検討しましょう。
空白や改行の扱い
空白や改行の違いも差分として検出されます。
これらを無視したい場合は、前処理で正規化すると良いでしょう。
# 空白を正規化する例
def normalize_whitespace(text):
return ' '.join(text.split())
# 使用例
text1_normalized = normalize_whitespace(text1)
text2_normalized = normalize_whitespace(text2)
行単位以外の比較
difflibは主に行単位の比較ですが、単語単位や文字単位の比較もできます。
# 文字単位の比較
import difflib
word1 = "プログラミング"
word2 = "プログラマー"
matcher = difflib.SequenceMatcher(None, word1, word2)
print(f"類似度: {matcher.ratio():.2%}")
# どの部分が一致しているか
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
if tag == 'equal':
print(f"一致: {word1[i1:i2]}")
まとめ
Pythonのdifflibを使えば、様々な形式で差分を取得できます。
基本的な使い方から実践的な活用まで、段階的にマスターしていけば大丈夫です。
unified_diff()
で基本的な差分表示HtmlDiff()
で視覚的な表示SequenceMatcher()
で詳細な比較
これらのツールを使いこなすことで、コードレビューや変更管理がぐっと楽になります。
最初は簡単な文字列比較から始めて、徐々に複雑な比較にチャレンジしてみてください。
著者について

とまだ
フルスタックエンジニア
Learning Next の創設者。Ruby on Rails と React を中心に、プログラミング教育に情熱を注いでいます。初心者が楽しく学べる環境作りを目指しています。
著者の詳細を見る →