difflib --- 計(jì)算差異的輔助工具?

源代碼: Lib/difflib.py


此模塊提供用于比較序列的類和函數(shù)。 例如,它可被用于比較文件,并可產(chǎn)生多種格式的不同文件差異信息,包括 HTML 和上下文以及統(tǒng)一的 diff 數(shù)據(jù)。 有關(guān)比較目錄和文件,另請參閱 filecmp 模塊。

class difflib.SequenceMatcher

這是一個(gè)靈活的類,可用于比較任何類型的序列對,只要序列元素為 hashable 對象。 其基本算法要早于由 Ratcliff 和 Obershelp 于 1980 年代末期發(fā)表并以“格式塔模式匹配”的夸張名稱命名的算法,并且更加有趣一些。 其思路是找到不包含“垃圾”元素的最長連續(xù)匹配子序列;所謂“垃圾”元素是指其在某種意義上沒有價(jià)值,例如空白行或空白符。 (處理垃圾元素是對 Ratcliff 和 Obershelp 算法的一個(gè)擴(kuò)展。) 然后同樣的思路將遞歸地應(yīng)用于匹配序列的左右序列片段。 這并不能產(chǎn)生最小編輯序列,但確實(shí)能產(chǎn)生在人們看來“正確”的匹配。

耗時(shí): 基本 Ratcliff-Obershelp 算法在最壞情況下為立方時(shí)間而在一般情況下為平方時(shí)間。 SequenceMatcher 在最壞情況下為平方時(shí)間而在一般情況下的行為受到序列中有多少相同元素這一因素的微妙影響;在最佳情況下則為線性時(shí)間。

自動(dòng)垃圾啟發(fā)式計(jì)算: SequenceMatcher 支持使用啟發(fā)式計(jì)算來自動(dòng)將特定序列項(xiàng)視為垃圾。 這種啟發(fā)式計(jì)算會(huì)統(tǒng)計(jì)每個(gè)單獨(dú)項(xiàng)在序列中出現(xiàn)的次數(shù)。 如果某一項(xiàng)(在第一項(xiàng)之后)的重復(fù)次數(shù)超過序列長度的 1% 并且序列長度至少有 200 項(xiàng),該項(xiàng)會(huì)被標(biāo)記為“熱門”并被視為序列匹配中的垃圾。 這種啟發(fā)式計(jì)算可以通過在創(chuàng)建 SequenceMatcher 時(shí)將 autojunk 參數(shù)設(shè)為 False 來關(guān)閉。

3.2 新版功能: autojunk 形參。

class difflib.Differ?

這個(gè)類的作用是比較由文本行組成的序列,并產(chǎn)生可供人閱讀的差異或增量信息。 Differ 統(tǒng)一使用 SequenceMatcher 來完成行序列的比較以及相似(接近匹配)行內(nèi)部字符序列的比較。

Differ 增量的每一行均以雙字母代碼打頭:

雙字母代碼

含意

'- '

行為序列 1 所獨(dú)有

'+ '

行為序列 2 所獨(dú)有

'  '

行在兩序列中相同

'? '

行不存在于任一輸入序列

Lines beginning with '?' attempt to guide the eye to intraline differences, and were not present in either input sequence. These lines can be confusing if the sequences contain whitespace characters, such as spaces, tabs or line breaks.

class difflib.HtmlDiff?

這個(gè)類可用于創(chuàng)建 HTML 表格(或包含表格的完整 HTML 文件)以并排地逐行顯示文本比較,行間與行外的更改將突出顯示。 此表格可以基于完全或上下文差異模式來生成。

這個(gè)類的構(gòu)造函數(shù):

__init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)?

初始化 HtmlDiff 的實(shí)例。

tabsize 是一個(gè)可選關(guān)鍵字參數(shù),指定制表位的間隔,默認(rèn)值為 8。

wrapcolumn 是一個(gè)可選關(guān)鍵字參數(shù),指定行文本自動(dòng)打斷并換行的列位置,默認(rèn)值為 None 表示不自動(dòng)換行。

linejunkcharjunk 均是可選關(guān)鍵字參數(shù),會(huì)傳入 ndiff() (被 HtmlDiff 用來生成并排顯示的 HTML 差異)。 請參閱 ndiff() 文檔了解參數(shù)默認(rèn)值及其說明。

下列是公開的方法

make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5, *, charset='utf-8')?

比較 fromlinestolines (字符串列表) 并返回一個(gè)字符串,表示一個(gè)完整 HTML 文件,其中包含各行差異的表格,行間與行外的更改將突出顯示。

fromdesctodesc 均是可選關(guān)鍵字參數(shù),指定來源/目標(biāo)文件的列標(biāo)題字符串(默認(rèn)均為空白字符串)。

contextnumlines 均是可選關(guān)鍵字參數(shù)。 當(dāng)只要顯示上下文差異時(shí)就將 context 設(shè)為 True,否則默認(rèn)值 False 為顯示完整文件。 numlines 默認(rèn)為 5。 當(dāng) contextTrue 時(shí) numlines 將控制圍繞突出顯示差異部分的上下文行數(shù)。 當(dāng) contextFalse 時(shí) numlines 將控制在使用 "next" 超鏈接時(shí)突出顯示差異部分之前所顯示的行數(shù)(設(shè)為零則會(huì)導(dǎo)致 "next" 超鏈接將下一個(gè)突出顯示差異部分放在瀏覽器頂端,不添加任何前導(dǎo)上下文)。

備注

fromdesctodesc 會(huì)被當(dāng)作未轉(zhuǎn)義的 HTML 來解讀,當(dāng)接收不可信來源的輸入時(shí)應(yīng)該適當(dāng)?shù)剡M(jìn)行轉(zhuǎn)義。

在 3.5 版更改: 增加了 charset 關(guān)鍵字參數(shù)。 HTML 文檔的默認(rèn)字符集從 'ISO-8859-1' 更改為 'utf-8'。

make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)?

比較 fromlinestolines (字符串列表) 并返回一個(gè)字符串,表示一個(gè)包含各行差異的完整 HTML 表格,行間與行外的更改將突出顯示。

此方法的參數(shù)與 make_file() 方法的相同。

Tools/scripts/diff.py 是這個(gè)類的命令行前端,其中包含一個(gè)很好的使用示例。

difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')?

比較 ab (字符串列表);返回上下文差異格式的增量信息 (一個(gè)產(chǎn)生增量行的 generator)。

所謂上下文差異是一種只顯示有更改的行再加幾個(gè)上下文行的緊湊形式。 更改被顯示為之前/之后的樣式。 上下文行數(shù)由 n 設(shè)定,默認(rèn)為三行。

默認(rèn)情況下,差異控制行(以 *** or --- 表示)是通過末尾換行符來創(chuàng)建的。 這樣做的好處是從 io.IOBase.readlines() 創(chuàng)建的輸入將得到適用于 io.IOBase.writelines() 的差異信息,因?yàn)檩斎牒洼敵龆紟в心┪矒Q行符。

對于沒有末尾換行符的輸入,應(yīng)將 lineterm 參數(shù)設(shè)為 "",這樣輸出內(nèi)容將統(tǒng)一不帶換行符。

上下文差異格式通常帶有一個(gè)記錄文件名和修改時(shí)間的標(biāo)頭。 這些信息的部分或全部可以使用字符串 fromfile, tofile, fromfiledatetofiledate 來指定。 修改時(shí)間通常以 ISO 8601 格式表示。 如果未指定,這些字符串默認(rèn)為空。

>>>
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py', tofile='after.py'))
*** before.py
--- after.py
***************
*** 1,4 ****
! bacon
! eggs
! ham
  guido
--- 1,4 ----
! python
! eggy
! hamster
  guido

請參閱 difflib 的命令行接口 獲取更詳細(xì)的示例。

difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)?

返回由最佳“近似”匹配構(gòu)成的列表。 word 為一個(gè)指定目標(biāo)近似匹配的序列(通常為字符串),possibilities 為一個(gè)由用于匹配 word 的序列構(gòu)成的列表(通常為字符串列表)。

可選參數(shù) n (默認(rèn)為 3) 指定最多返回多少個(gè)近似匹配; n 必須大于 0.

可選參數(shù) cutoff (默認(rèn)為 0.6) 是一個(gè) [0, 1] 范圍內(nèi)的浮點(diǎn)數(shù)。 與 word 相似度得分未達(dá)到該值的候選匹配將被忽略。

候選匹配中(不超過 n 個(gè))的最佳匹配將以列表形式返回,按相似度得分排序,最相似的排在最前面。

>>>
>>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'])
['apple', 'ape']
>>> import keyword
>>> get_close_matches('wheel', keyword.kwlist)
['while']
>>> get_close_matches('pineapple', keyword.kwlist)
[]
>>> get_close_matches('accept', keyword.kwlist)
['except']
difflib.ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK)?

比較 ab (字符串列表);返回 Differ 形式的增量信息 (一個(gè)產(chǎn)生增量行的 generator)。

可選關(guān)鍵字形參 linejunkcharjunk 均為過濾函數(shù) (或?yàn)?None):

linejunk: 此函數(shù)接受單個(gè)字符串參數(shù),如果其為垃圾字符串則返回真值,否則返回假值。 默認(rèn)為 None。 此外還有一個(gè)模塊層級的函數(shù) IS_LINE_JUNK(),它會(huì)過濾掉沒有可見字符的行,除非該行添加了至多一個(gè)井號符 ('#') -- 但是下層的 SequenceMatcher 類會(huì)動(dòng)態(tài)分析哪些行的重復(fù)頻繁到足以形成噪音,這通常會(huì)比使用此函數(shù)的效果更好。

charjunk: 此函數(shù)接受一個(gè)字符(長度為 1 的字符串),如果其為垃圾字符則返回真值,否則返回假值。 默認(rèn)為模塊層級的函數(shù) IS_CHARACTER_JUNK(),它會(huì)過濾掉空白字符(空格符或制表符;但包含換行符可不是個(gè)好主意?。?。

Tools/scripts/ndiff.py 是這個(gè)函數(shù)的命令行前端。

>>>
>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
...              'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> print(''.join(diff), end="")
- one
?  ^
+ ore
?  ^
- two
- three
?  -
+ tree
+ emu
difflib.restore(sequence, which)?

返回兩個(gè)序列中產(chǎn)生增量的那一個(gè)。

給出一個(gè)由 Differ.compare()ndiff() 產(chǎn)生的 序列,提取出來自文件 1 或 2 (which 形參) 的行,去除行前綴。

示例:

>>>
>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
...              'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> diff = list(diff) # materialize the generated delta into a list
>>> print(''.join(restore(diff, 1)), end="")
one
two
three
>>> print(''.join(restore(diff, 2)), end="")
ore
tree
emu
difflib.unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')?

比較 ab (字符串列表);返回統(tǒng)一差異格式的增量信息 (一個(gè)產(chǎn)生增量行的 generator)。

所以統(tǒng)一差異是一種只顯示有更改的行再加幾個(gè)上下文行的緊湊形式。 更改被顯示為內(nèi)聯(lián)的樣式(而不是分開的之前/之后文本塊)。 上下文行數(shù)由 n 設(shè)定,默認(rèn)為三行。

默認(rèn)情況下,差異控制行 (以 ---, +++@@ 表示) 是通過末尾換行符來創(chuàng)建的。 這樣做的好處是從 io.IOBase.readlines() 創(chuàng)建的輸入將得到適用于 io.IOBase.writelines() 的差異信息,因?yàn)檩斎牒洼敵龆紟в心┪矒Q行符。

對于沒有末尾換行符的輸入,應(yīng)將 lineterm 參數(shù)設(shè)為 "",這樣輸出內(nèi)容將統(tǒng)一不帶換行符。

上下文差異格式通常帶有一個(gè)記錄文件名和修改時(shí)間的標(biāo)頭。 這些信息的部分或全部可以使用字符串 fromfile, tofile, fromfiledatetofiledate 來指定。 修改時(shí)間通常以 ISO 8601 格式表示。 如果未指定,這些字符串默認(rèn)為空。

>>>
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(unified_diff(s1, s2, fromfile='before.py', tofile='after.py'))
--- before.py
+++ after.py
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
 guido

請參閱 difflib 的命令行接口 獲取更詳細(xì)的示例。

difflib.diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n')?

使用 dfunc 比較 ab (字節(jié)串對象列表);產(chǎn)生以 dfunc 所返回格式表示的差異行列表(也是字節(jié)串)。 dfunc 必須是可調(diào)用對象,通常為 unified_diff()context_diff()。

允許你比較編碼未知或不一致的數(shù)據(jù)。 除 n 之外的所有輸入都必須為字節(jié)串對象而非字符串。 作用方式為無損地將所有輸入 (除 n 之外) 轉(zhuǎn)換為字符串,并調(diào)用 dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)。 dfunc 的輸出會(huì)被隨即轉(zhuǎn)換回字節(jié)串,這樣你所得到的增量行將具有與 ab 相同的未知/不一致編碼。

3.5 新版功能.

difflib.IS_LINE_JUNK(line)?

對于可忽略的行返回 True。 如果 line 為空行或只包含單個(gè) '#'line 行就是可忽略的,否則就是不可忽略的。 此函數(shù)被用作較舊版本 ndiff()linejunk 形參的默認(rèn)值。

difflib.IS_CHARACTER_JUNK(ch)?

對于可忽略的字符返回 True。 字符 ch 如果為空格符或制表符則 ch 就是可忽略的,否則就是不可忽略的。 此函數(shù)被用作 ndiff()charjunk 形參的默認(rèn)值。

參見

模式匹配:格式塔方法

John W. Ratcliff 和 D. E. Metzener 對于一種類似算法的討論。 此文于 1988 年 7 月發(fā)表于 Dr. Dobb's Journal

SequenceMatcher 對象?

SequenceMatcher 類具有這樣的構(gòu)造器:

class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)?

可選參數(shù) isjunk 必須為 None (默認(rèn)值) 或?yàn)榻邮芤粋€(gè)序列元素并當(dāng)且僅當(dāng)其為應(yīng)忽略的“垃圾”元素時(shí)返回真值的單參數(shù)函數(shù)。 傳入 None 作為 isjunk 的值就相當(dāng)于傳入 lambda x: False;也就是說不忽略任何值。 例如,傳入:

lambda x: x in " \t"

如果你以字符序列的形式對行進(jìn)行比較,并且不希望區(qū)分空格符或硬制表符。

可選參數(shù) ab 為要比較的序列;兩者默認(rèn)為空字符串。 兩個(gè)序列的元素都必須為 hashable。

可選參數(shù) autojunk 可用于啟用自動(dòng)垃圾啟發(fā)式計(jì)算。

3.2 新版功能: autojunk 形參。

SequenceMatcher 對象接受三個(gè)數(shù)據(jù)屬性: bjunkb 當(dāng)中 isjunkTrue 的元素集合;bpopular 是被啟發(fā)式計(jì)算(如果其未被禁用)視為熱門候選的非垃圾元素集合;b2j 是將 b 當(dāng)中剩余元素映射到一個(gè)它們出現(xiàn)位置列表的字典。 所有三個(gè)數(shù)據(jù)屬性將在 b 通過 set_seqs()set_seq2() 重置時(shí)被重置。

3.2 新版功能: bjunkbpopular 屬性。

SequenceMatcher 對象具有以下方法:

set_seqs(a, b)?

設(shè)置要比較的兩個(gè)序列。

SequenceMatcher 計(jì)算并緩存有關(guān)第二個(gè)序列的詳細(xì)信息,這樣如果你想要將一個(gè)序列與多個(gè)序列進(jìn)行比較,可使用 set_seq2() 一次性地設(shè)置該常用序列并重復(fù)地對每個(gè)其他序列各調(diào)用一次 set_seq1()

set_seq1(a)?

設(shè)置要比較的第一個(gè)序列。 要比較的第二個(gè)序列不會(huì)改變。

set_seq2(b)?

設(shè)置要比較的第二個(gè)序列。 要比較的第一個(gè)序列不會(huì)改變。

find_longest_match(alo=0, ahi=None, blo=0, bhi=None)?

找出 a[alo:ahi]b[blo:bhi] 中的最長匹配塊。

如果 isjunk 被省略或?yàn)?Nonefind_longest_match() 將返回 (i, j, k) 使得 a[i:i+k] 等于 b[j:j+k],其中 alo <= i <= i+k <= ahi 并且 blo <= j <= j+k <= bhi。 對于所有滿足這些條件的 (i', j', k'),如果 i == i', j <= j' 也被滿足,則附加條件 k >= k', i <= i'。 換句話說,對于所有最長匹配塊,返回在 a 當(dāng)中最先出現(xiàn)的一個(gè),而對于在 a 當(dāng)中最先出現(xiàn)的所有最長匹配塊,則返回在 b 當(dāng)中最先出現(xiàn)的一個(gè)。

>>>
>>> s = SequenceMatcher(None, " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
Match(a=0, b=4, size=5)

如果提供了 isjunk,將按上述規(guī)則確定第一個(gè)最長匹配塊,但額外附加不允許塊內(nèi)出現(xiàn)垃圾元素的限制。 然后將通過(僅)匹配兩邊的垃圾元素來盡可能地?cái)U(kuò)展該塊。 這樣結(jié)果塊絕對不會(huì)匹配垃圾元素,除非同樣的垃圾元素正好與有意義的匹配相鄰。

這是與之前相同的例子,但是將空格符視為垃圾。 這將防止 ' abcd' 直接與第二個(gè)序列末尾的 ' abcd' 相匹配。 而只可以匹配 'abcd',并且是匹配第二個(gè)序列最左邊的 'abcd'

>>>
>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
Match(a=1, b=0, size=4)

如果未找到匹配塊,此方法將返回 (alo, blo, 0)。

此方法將返回一個(gè) named tuple Match(a, b, size)

在 3.9 版更改: 加入默認(rèn)參數(shù)。

get_matching_blocks()?

返回描述非重疊匹配子序列的三元組列表。 每個(gè)三元組的形式為 (i, j, n),其含義為 a[i:i+n] == b[j:j+n]。 這些三元組按 ij 單調(diào)遞增排列。

最后一個(gè)三元組用于占位,其值為 (len(a), len(b), 0)。 它是唯一 n == 0 的三元組。 如果 (i, j, n)(i', j', n') 是在列表中相鄰的三元組,且后者不是列表中的最后一個(gè)三元組,則 i+n < i'j+n < j';換句話說,相鄰的三元組總是描述非相鄰的相等塊。

>>>
>>> s = SequenceMatcher(None, "abxcd", "abcd")
>>> s.get_matching_blocks()
[Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]
get_opcodes()?

返回描述如何將 a 變?yōu)?b 的 5 元組列表,每個(gè)元組的形式為 (tag, i1, i2, j1, j2)。 在第一個(gè)元組中 i1 == j1 == 0,而在其余的元組中 i1 等于前一個(gè)元組的 i2,并且 j1 也等于前一個(gè)元組的 j2。

tag 值為字符串,其含義如下:

含意

'replace'

a[i1:i2] 應(yīng)由 b[j1:j2] 替換。

'delete'

a[i1:i2] 應(yīng)被刪除。 請注意在此情況下 j1 == j2。

'insert'

b[j1:j2] 應(yīng)插入到 a[i1:i1]。 請注意在此情況下 i1 == i2。

'equal'

a[i1:i2] == b[j1:j2] (兩個(gè)子序列相同)。

例如:

>>>
>>> a = "qabxcd"
>>> b = "abycdf"
>>> s = SequenceMatcher(None, a, b)
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
...     print('{:7}   a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(
...         tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))
delete    a[0:1] --> b[0:0]      'q' --> ''
equal     a[1:3] --> b[0:2]     'ab' --> 'ab'
replace   a[3:4] --> b[2:3]      'x' --> 'y'
equal     a[4:6] --> b[3:5]     'cd' --> 'cd'
insert    a[6:6] --> b[5:6]       '' --> 'f'
get_grouped_opcodes(n=3)?

返回一個(gè)帶有最多 n 行上下文的分組的 generator。

get_opcodes() 所返回的組開始,此方法會(huì)拆分出較小的更改簇并消除沒有更改的間隔區(qū)域。

這些分組以與 get_opcodes() 相同的格式返回。

ratio()?

返回一個(gè)取值范圍 [0, 1] 的浮點(diǎn)數(shù)作為序列相似性度量。

其中 T 是兩個(gè)序列中元素的總數(shù)量,M 是匹配的數(shù)量,即 2.0*M / T。 請注意如果兩個(gè)序列完全相同則該值為 1.0,如果兩者完全不同則為 0.0。

如果 get_matching_blocks()get_opcodes() 尚未被調(diào)用則此方法運(yùn)算消耗較大,在此情況下你可能需要先調(diào)用 quick_ratio()real_quick_ratio() 來獲取一個(gè)上界。

備注

注意: ratio() 調(diào)用的結(jié)果可能會(huì)取決于參數(shù)的順序。 例如:

>>>
>>> SequenceMatcher(None, 'tide', 'diet').ratio()
0.25
>>> SequenceMatcher(None, 'diet', 'tide').ratio()
0.5
quick_ratio()?

相對快速地返回一個(gè) ratio() 的上界。

real_quick_ratio()?

非??焖俚胤祷匾粋€(gè) ratio() 的上界。

這三個(gè)返回匹配部分占字符總數(shù)的比率的方法可能由于不同的近似級別而給出不一樣的結(jié)果,但是 quick_ratio()real_quick_ratio() 總是會(huì)至少與 ratio() 一樣大:

>>>
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0

SequenceMatcher 的示例?

以下示例比較兩個(gè)字符串,并將空格視為“垃圾”:

>>>
>>> s = SequenceMatcher(lambda x: x == " ",
...                     "private Thread currentThread;",
...                     "private volatile Thread currentThread;")

ratio() 返回一個(gè) [0, 1] 范圍內(nèi)的整數(shù)作為兩個(gè)序列相似性的度量。 根據(jù)經(jīng)驗(yàn),ratio() 值超過 0.6 就意味著兩個(gè)序列是近似匹配的:

>>>
>>> print(round(s.ratio(), 3))
0.866

如果你只對兩個(gè)序列相匹配的位置感興趣,則 get_matching_blocks() 就很方便:

>>>
>>> for block in s.get_matching_blocks():
...     print("a[%d] and b[%d] match for %d elements" % block)
a[0] and b[0] match for 8 elements
a[8] and b[17] match for 21 elements
a[29] and b[38] match for 0 elements

請注意 get_matching_blocks() 返回的最后一個(gè)元組總是只用于占位的 (len(a), len(b), 0),這也是元組末尾元素(匹配的元素?cái)?shù)量)為 0 的唯一情況。

如果你想要知道如何將第一個(gè)序列轉(zhuǎn)成第二個(gè)序列,可以使用 get_opcodes():

>>>
>>> for opcode in s.get_opcodes():
...     print("%6s a[%d:%d] b[%d:%d]" % opcode)
 equal a[0:8] b[0:8]
insert a[8:8] b[8:17]
 equal a[8:29] b[17:38]

參見

Differ 對象?

請注意 Differ 所生成的增量并不保證是 最小 差異。 相反,最小差異往往是違反直覺的,因?yàn)樗鼈儠?huì)同步任何可能的地方,有時(shí)甚至意外產(chǎn)生相距 100 頁的匹配。 將同步點(diǎn)限制為連續(xù)匹配保留了一些局部性概念,這偶爾會(huì)帶來產(chǎn)生更長差異的代價(jià)。

Differ 類具有這樣的構(gòu)造器:

class difflib.Differ(linejunk=None, charjunk=None)

可選關(guān)鍵字形參 linejunkcharjunk 均為過濾函數(shù) (或?yàn)?None):

linejunk: 接受單個(gè)字符串作為參數(shù)的函數(shù),如果其為垃圾字符串則返回真值。 默認(rèn)值為 None,意味著沒有任何行會(huì)被視為垃圾行。

charjunk: 接受單個(gè)字符(長度為 1 的字符串)作為參數(shù)的函數(shù),如果其為垃圾字符則返回真值。 默認(rèn)值為 None,意味著沒有任何字符會(huì)被視為垃圾字符。

這些垃圾過濾函數(shù)可加快查找差異的匹配速度,并且不會(huì)導(dǎo)致任何差異行或字符被忽略。 請閱讀 find_longest_match() 方法的 isjunk 形參的描述了解詳情。

Differ 對象是通過一個(gè)單獨(dú)方法來使用(生成增量)的:

compare(a, b)?

比較兩個(gè)由行組成的序列,并生成增量(一個(gè)由行組成的序列)。

每個(gè)序列必須包含一個(gè)以換行符結(jié)尾的單行字符串。 這樣的序列可以通過文件類對象的 readlines() 方法來獲取。 所生成的增量同樣由以換行符結(jié)尾的字符串構(gòu)成,可以通過文件類對象的 writelines() 方法原樣打印出來。

Differ 示例?

此示例比較兩段文本。 首先我們設(shè)置文本為以換行符結(jié)尾的單行字符串構(gòu)成的序列(這樣的序列也可以通過文件類對象的 readlines() 方法來獲?。?/p>

>>>
>>> text1 = '''  1. Beautiful is better than ugly.
...   2. Explicit is better than implicit.
...   3. Simple is better than complex.
...   4. Complex is better than complicated.
... '''.splitlines(keepends=True)
>>> len(text1)
4
>>> text1[0][-1]
'\n'
>>> text2 = '''  1. Beautiful is better than ugly.
...   3.   Simple is better than complex.
...   4. Complicated is better than complex.
...   5. Flat is better than nested.
... '''.splitlines(keepends=True)

接下來我們實(shí)例化一個(gè) Differ 對象:

>>>
>>> d = Differ()

請注意在實(shí)例化 Differ 對象時(shí)我們可以傳入函數(shù)來過濾掉“垃圾”行和字符。 詳情參見 Differ() 構(gòu)造器說明。

最后,我們比較兩個(gè)序列:

>>>
>>> result = list(d.compare(text1, text2))

result 是一個(gè)字符串列表,讓我們將其美化打印出來:

>>>
>>> from pprint import pprint
>>> pprint(result)
['    1. Beautiful is better than ugly.\n',
 '-   2. Explicit is better than implicit.\n',
 '-   3. Simple is better than complex.\n',
 '+   3.   Simple is better than complex.\n',
 '?     ++\n',
 '-   4. Complex is better than complicated.\n',
 '?            ^                     ---- ^\n',
 '+   4. Complicated is better than complex.\n',
 '?           ++++ ^                      ^\n',
 '+   5. Flat is better than nested.\n']

作為單獨(dú)的多行字符串顯示出來則是這樣:

>>>
>>> import sys
>>> sys.stdout.writelines(result)
    1. Beautiful is better than ugly.
-   2. Explicit is better than implicit.
-   3. Simple is better than complex.
+   3.   Simple is better than complex.
?     ++
-   4. Complex is better than complicated.
?            ^                     ---- ^
+   4. Complicated is better than complex.
?           ++++ ^                      ^
+   5. Flat is better than nested.

difflib 的命令行接口?

這個(gè)實(shí)例演示了如何使用 difflib 來創(chuàng)建一個(gè)類似于 diff 的工具。 它同樣包含在 Python 源碼發(fā)布包中,文件名為 Tools/scripts/diff.py。

#!/usr/bin/env python3
""" Command line interface to difflib.py providing diffs in four formats:

* ndiff:    lists every line and highlights interline changes.
* context:  highlights clusters of changes in a before/after format.
* unified:  highlights clusters of changes in an inline format.
* html:     generates side by side comparison with change highlights.

"""

import sys, os, difflib, argparse
from datetime import datetime, timezone

def file_mtime(path):
    t = datetime.fromtimestamp(os.stat(path).st_mtime,
                               timezone.utc)
    return t.astimezone().isoformat()

def main():

    parser = argparse.ArgumentParser()
    parser.add_argument('-c', action='store_true', default=False,
                        help='Produce a context format diff (default)')
    parser.add_argument('-u', action='store_true', default=False,
                        help='Produce a unified format diff')
    parser.add_argument('-m', action='store_true', default=False,
                        help='Produce HTML side by side diff '
                             '(can use -c and -l in conjunction)')
    parser.add_argument('-n', action='store_true', default=False,
                        help='Produce a ndiff format diff')
    parser.add_argument('-l', '--lines', type=int, default=3,
                        help='Set number of context lines (default 3)')
    parser.add_argument('fromfile')
    parser.add_argument('tofile')
    options = parser.parse_args()

    n = options.lines
    fromfile = options.fromfile
    tofile = options.tofile

    fromdate = file_mtime(fromfile)
    todate = file_mtime(tofile)
    with open(fromfile) as ff:
        fromlines = ff.readlines()
    with open(tofile) as tf:
        tolines = tf.readlines()

    if options.u:
        diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
    elif options.n:
        diff = difflib.ndiff(fromlines, tolines)
    elif options.m:
        diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n)
    else:
        diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)

    sys.stdout.writelines(diff)

if __name__ == '__main__':
    main()