Python の difflib で空白文字や数字、大文字小文字の変化を無視する方法

Python の difflib を利用すれば、二つのテキストの差分を表示することができます。

まずは difflib を普通に使ってみる

一般的な使い方は以下のような物です。

# coding: utf-8
import difflib

# 元の文字列
a = "11\n\t\t\nheLLo\n12a34"
b = "22\n  \nHELLO\n56b78"

# 行で区切った list を作ります
a = a.splitlines()
b = b.splitlines()

# 変化を表示します
d = difflib.Differ()
print "\n".join(list(d.compare(a, b)))

結果は以下の通りです

- 11
- 		
- heLLo
- 12a34
+ 22
+   
+ HELLO
+ 56b78

テキストのどの行に変化があったかを知ることができました。

大文字小文字や空白文字、数字など、特定の文字を無視したい場合

方法1: 事前に無視したい文字を取り除く

一つ目の方法は差分をとる前に無視したい文字を取り除いておきます。

# coding: utf-8
import difflib
import re  # 正規表現で数字を探すため

# 元の文字列
a = "11\n\t\t\nheLLo\n12a34"
b = "22\n  \nHELLO\n56b78"

# 不要な文字を削除
# まずは空白を削除
a = a.replace("\t", "").replace(" ", "")
b = b.replace("\t", "").replace(" ", "")

# 続いて、数字を削除
a = re.sub(r"[0-9]+", "", a)
b = re.sub(r"[0-9]+", "", b)

# すべて小文字に変換
a = a.lower()
b = b.lower()

# 行で区切った list を作ります
a = a.splitlines()
b = b.splitlines()

# 変化を表示します
d = difflib.Differ()
print "\n".join(list(d.compare(a, b)))

結果は以下の通りです。

  
  
  hello
- a
+ b

目的は達成できたのですが、多くのの情報が削られてしまい、文脈の判断ができなくなりました。

方法2: クラス継承によって数字や空白に無関心な文字列クラスを作る

もう一つの方法は、文字列クラスを継承して __eq__ メソッドをオーバーライドします。
これによって、元の文字列を保持したまま、差分対象の比較が可能になります。

# coding: utf-8
import difflib
import re  # 正規表現で数字を探すため

# 元の文字列
a = "11\n\t\t\nheLLo\n12a34"
b = "22\n  \nHELLO\n56b78"

# str クラスを継承した数字や空白に無関心な文字列クラスを作ります。
class InsensitiveStr(str):
    def __eq__(self, other):
    	# 不要な文字を削除
    	# まずは空白を削除
    	a = self.replace("\t", "").replace(" ", "")   # a は str になる
    	b = other.replace("\t", "").replace(" ", "")  # b は str になる

    	# 続いて、数字を削除
    	a = re.sub(r"[0-9]+", "", a)
    	b = re.sub(r"[0-9]+", "", b)

    	return a.lower() == b.lower()  # str 同士の比較を行う

# 行で区切った list を作り、InsensitiveStr でラップします
a = [InsensitiveStr(line) for line in a.splitlines()]
b = [InsensitiveStr(line) for line in b.splitlines()]

# 変化を表示します
d = difflib.Differ()
print "\n".join(list(d.compare(a, b)))

結果は以下の通りです。

  11
  		
  heLLo
- 12a34
+ 56b78

大文字小文字、数字や空白文字は変化していないものとして扱われ、元の行が表示されています。
方法1と違い、数字や空白文字の情報が残るため、文脈の判断がしやすくなりました。
また、方法1では heLLo が hello になってしまっていたのに対し、方法2では元の heLLo のまま表示されるようになりました。