difflib --- 計算差異的輔助工具?

源代碼: Lib/difflib.py


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

class difflib.SequenceMatcher

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

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

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

3.2 新版功能: autojunk 形參。

class difflib.Differ?

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

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

雙字母代碼

含意

'- '

行為序列 1 所獨有

'+ '

行為序列 2 所獨有

'  '

行在兩序列中相同

'? '

行不存在于任一輸入序列

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

這個(gè)類(lèi)的構造函數:

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

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

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

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

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

下列是公開(kāi)的方法

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

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

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

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

備注

fromdesctodesc 會(huì )被當作未轉義的 HTML 來(lái)解讀,當接收不可信來(lái)源的輸入時(shí)應該適當地進(jìn)行轉義。

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

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

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

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

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

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

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

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

默認情況下,差異控制行(以 *** or --- 表示)是通過(guò)末尾換行符來(lái)創(chuàng )建的。 這樣做的好處是從 io.IOBase.readlines() 創(chuàng )建的輸入將得到適用于 io.IOBase.writelines() 的差異信息,因為輸入和輸出都帶有末尾換行符。

對于沒(méi)有末尾換行符的輸入,應將 lineterm 參數設為 "",這樣輸出內容將統一不帶換行符。

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

>>>
>>> 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 的命令行接口 獲取更詳細的示例。

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

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

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

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

候選匹配中(不超過(guò) 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 均為過(guò)濾函數 (或為 None):

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

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

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

>>>
>>> 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)生的 序列,提取出來(lái)自文件 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 (字符串列表);返回統一差異格式的增量信息 (一個(gè)產(chǎn)生增量行的 generator)。

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

默認情況下,差異控制行 (以 ---, +++@@ 表示) 是通過(guò)末尾換行符來(lái)創(chuàng )建的。 這樣做的好處是從 io.IOBase.readlines() 創(chuàng )建的輸入將得到適用于 io.IOBase.writelines() 的差異信息,因為輸入和輸出都帶有末尾換行符。

對于沒(méi)有末尾換行符的輸入,應將 lineterm 參數設為 "",這樣輸出內容將統一不帶換行符。

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

>>>
>>> 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 的命令行接口 獲取更詳細的示例。

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

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

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

3.5 新版功能.

difflib.IS_LINE_JUNK(line)?

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

difflib.IS_CHARACTER_JUNK(ch)?

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

參見(jiàn)

模式匹配:格式塔方法

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

SequenceMatcher 對象?

SequenceMatcher 類(lèi)具有這樣的構造器:

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

可選參數 isjunk 必須為 None (默認值) 或為接受一個(gè)序列元素并當且僅當其為應忽略的“垃圾”元素時(shí)返回真值的單參數函數。 傳入 None 作為 isjunk 的值就相當于傳入 lambda x: False;也就是說(shuō)不忽略任何值。 例如,傳入:

lambda x: x in " \t"

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

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

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

3.2 新版功能: autojunk 形參。

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

3.2 新版功能: bjunkbpopular 屬性。

SequenceMatcher 對象具有以下方法:

set_seqs(a, b)?

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

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

set_seq1(a)?

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

set_seq2(b)?

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

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

找出 a[alo:ahi]b[blo:bhi] 中的最長(cháng)匹配塊。

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

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

如果提供了 isjunk,將按上述規則確定第一個(gè)最長(cháng)匹配塊,但額外附加不允許塊內出現垃圾元素的限制。 然后將通過(guò)(僅)匹配兩邊的垃圾元素來(lái)盡可能地擴展該塊。 這樣結果塊絕對不會(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 版更改: 加入默認參數。

get_matching_blocks()?

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

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

>>>
>>> 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] 應由 b[j1:j2] 替換。

'delete'

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

'insert'

b[j1:j2] 應插入到 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() 所返回的組開(kāi)始,此方法會(huì )拆分出較小的更改簇并消除沒(méi)有更改的間隔區域。

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

ratio()?

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

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

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

備注

注意: ratio() 調用的結果可能會(huì )取決于參數的順序。 例如:

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

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

real_quick_ratio()?

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

這三個(gè)返回匹配部分占字符總數的比率的方法可能由于不同的近似級別而給出不一樣的結果,但是 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] 范圍內的整數作為兩個(gè)序列相似性的度量。 根據經(jīng)驗,ratio() 值超過(guò) 0.6 就意味著(zhù)兩個(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),這也是元組末尾元素(匹配的元素數量)為 0 的唯一情況。

如果你想要知道如何將第一個(gè)序列轉成第二個(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]

參見(jiàn)

Differ 對象?

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

Differ 類(lèi)具有這樣的構造器:

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

可選關(guān)鍵字形參 linejunkcharjunk 均為過(guò)濾函數 (或為 None):

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

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

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

Differ 對象是通過(guò)一個(gè)單獨方法來(lái)使用(生成增量)的:

compare(a, b)?

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

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

Differ 示例?

此示例比較兩段文本。 首先我們設置文本為以換行符結尾的單行字符串構成的序列(這樣的序列也可以通過(guò)文件類(lèi)對象的 readlines() 方法來(lái)獲?。?/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)

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

>>>
>>> d = Differ()

請注意在實(shí)例化 Differ 對象時(shí)我們可以傳入函數來(lái)過(guò)濾掉“垃圾”行和字符。 詳情參見(jiàn) Differ() 構造器說(shuō)明。

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

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

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

>>>
>>> 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']

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

>>>
>>> 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 來(lái)創(chuàng )建一個(gè)類(lèi)似于 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()