1. difflib差異計算工具
此模塊提供用於比較序列的類和函數。 例如,它可以用於比較文件,並可以產生各種格式的不同信息,包括HTML和上下文以及統一格式的差異點。有關目錄和文件的比較,請參見filecmp模塊。
class difflib.SequenceMatcher(None,s1,s2)
這是一個靈活的類,可用於比較任何類型的序列對,只要序列元素為 hashable 對象。 其基本算法要早於由 Ratcliff 和 Obershelp 於 1980 年代末期發表並以“格式塔模式匹配”的誇張名稱命名的算法,並且更加有趣一些。 其思路是找到不包含“垃圾”元素的最長連續匹配子序列;所謂“垃圾”元素是指其在某種意義上沒有價值,例如空白行或空白符。 (處理垃圾元素是對 Ratcliff 和 Obershelp 算法的一個擴展。) 然后同樣的思路將遞歸地應用於匹配序列的左右序列片段。 這並不能產生最小編輯序列,但確實能產生在人們看來“正確”的匹配。
1.1 比較文本體
differ類用於處理文本行序列,並生成人類可讀的差異(deltas)或更改指令各行中的差異。differ生成的默認輸出與unix下的diff命令行工具類似,包括表的原始輸入值(包含共同的值),以及指示做了哪些更改的標記數據。
有 - 前綴的行在
第一個序列中,而非第二個序列。
有 + 前綴的行在第二個序列中,而非第一個序列。
如果某一行的版本之間存在增量差異,那么會使用一個加 ? 前綴以突出在新版本中的更改。
如果一行未改變,則會打印輸出,而且其左列有一個額外的空格,使它與其有差異的輸出對齊。
將文本傳入compare()之前先將其分解為由單個文本行構成的序列,與傳入串相比,這樣可以生成更可讀的輸出。
import difflib text1 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convalis. Nam sed sem vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus.""" text1_lines = text1.splitlines() text2 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convalis. Nam cras vitae mi vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus. Suspendisse eu lectus. In nunc.""" text2_lines = text2.splitlines() d = difflib.Differ() diff = d.compare(text1_lines, text2_lines) print('\n'.join(diff))
結果:
示例數據中兩個文本段的開始部分是一樣的,所以第一行會直接打印而沒有任何額外標注。
數據的第三行有變化,修改后的文本中包含有一個逗號。這兩個版本的數據行都會打印,而且第五行上的額外信息會顯示文本中哪一列有修改,這里顯示增加了 , 字符。
輸出中接下來幾行顯示刪除了一個多余的空格。
接下來有一個更復雜的改變,其替換了一個短語中的多個單詞。
段落中最后一句變化最大,所以表示差異時完全刪除了老版本,增加了新版本。
ndiff()函數生成的輸出基本上相同,通過特別“加工”來處理文本數據,並刪除輸入中的“噪聲”。
differ()類會顯示所有輸入行,統一差異格式(unified diff)則不同,它只包含有修改的文本行和一些上下文。unified_diff()函數會生成這種輸出。
import difflib text1 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convalis. Nam sed sem vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus.""" text1_lines = text1.splitlines() text2 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convalis. Nam cras vitae mi vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus. Suspendisse eu lectus. In nunc.""" text2_lines = text2.splitlines() diff = difflib.unified_diff( text1_lines, text2_lines, lineterm='', ) print('\n'.join(diff))
lineterm參數用來告訴unified_diff()不必為它返回的控制行追加換行符,因為輸入行不包括這些換行符。打印時所有行都會增加換行符。對於很多常用版本控制工具的用戶來說,輸出看上去應該很熟悉。
使用context_diff()會產生類似的可續輸出。
1.2 無用數據
所有生成差異序列的函數都會接受一些參數來指示應當忽略哪些行,以及要忽略一行中的哪些字符。例如,這些參數可用於跳過文件兩個版本中的標記或空白符改變。
from difflib import SequenceMatcher def show_results(match): print(' a = {}'.format(match.a)) print(' b = {}'.format(match.b)) print(' size = {}'.format(match.size)) i, j, k = match print(' A[a:a+size] = {!r}'.format(A[i:i + k])) print(' B[b:b+size] = {!r}'.format(B[j:j + k])) A = " abcd" B = "abcd abcd" print('A = {!r}'.format(A)) print('B = {!r}'.format(B)) print('\nWithout junk detection:') s1 = SequenceMatcher(None, A, B) match1 = s1.find_longest_match(0, len(A), 0, len(B)) show_results(match1) print('\nTreat spaces as junk:') s2 = SequenceMatcher(lambda x: x == " ", A, B) match2 = s2.find_longest_match(0, len(A), 0, len(B)) show_results(match2)
默認differ不會顯式地忽略任何行或字符,但會依賴SequenceMatcher的能力檢測噪聲。ndiff()的默認行為是忽略空格和制表符。
1.3 比較任意類型
SequenceMatcher類可以比較任意類型的兩個序列,只要它們的值是可散列的。這個類使用一個算法來標識序列中最長的連續匹配塊,並刪除對實際數據沒有貢獻的無用值。
函數get_opcodes()返回一個指令列表來修改第一個序列,使它與第二個序列匹配。這些指令被編碼為5元素元組,包括一個字符串指令(“操作碼”)和序列的兩對開始及結束索引(表示為i1、i2、j1和j2)。
值 |
意義 |
---|---|
|
|
|
|
|
|
|
|
import difflib s1 = [1, 2, 3, 5, 6, 4] s2 = [2, 3, 5, 4, 6, 1] print('Initial data:') print('s1 =', s1) print('s2 =', s2) print('s1 == s2:', s1 == s2) print() matcher = difflib.SequenceMatcher(None, s1, s2) for tag, i1, i2, j1, j2 in reversed(matcher.get_opcodes()): if tag == 'delete': print('Remove {} from positions [{}:{}]'.format( s1[i1:i2], i1, i2)) print(' before =', s1) del s1[i1:i2] elif tag == 'equal': print('s1[{}:{}] and s2[{}:{}] are the same'.format( i1, i2, j1, j2)) elif tag == 'insert': print('Insert {} from s2[{}:{}] into s1 at {}'.format( s2[j1:j2], j1, j2, i1)) print(' before =', s1) s1[i1:i2] = s2[j1:j2] elif tag == 'replace': print(('Replace {} from s1[{}:{}] ' 'with {} from s2[{}:{}]').format( s1[i1:i2], i1, i2, s2[j1:j2], j1, j2)) print(' before =', s1) s1[i1:i2] = s2[j1:j2] print(' after =', s1, '\n') print('s1 == s2:', s1 == s2)
這個例子比較了兩個整數列表,並使用get_opcodes()得出將原列表轉換為新列表的指令。這里以逆序應用所做的修改,以便增加和刪除元素之后列表索引仍是正確的。
SequenceMatcher用於處理定制類以及內置類型,前提是它們必須是可散列的。