difflib
— 用於計算差異的輔助工具¶
原始碼: Lib/difflib.py
此模組提供用於比較序列的類和函式。它可被用於比較檔案,並能以多種格式生成檔案差異資訊,包括 HTML、上下文(context)和統一(unified)差異格式。要比較目錄和檔案,另請參閱 filecmp
模組。
- class difflib.SequenceMatcher
這是一個靈活的類,用於比較任意型別的序列對,只要序列中的元素是可雜湊的。其基本演算法比 Ratcliff 和 Obershelp 在 20 世紀 80 年代末發表的演算法更早出現,也更精巧一些,那篇論文起了一個誇張的名字“格式塔模式匹配”(gestalt pattern matching)。該演算法的思想是,找到不包含任何“垃圾”元素的最長連續匹配子序列;這些“垃圾”元素是指在某種意義上不感興趣的元素,例如空行或空格。(處理垃圾是 Ratcliff 和 Obershelp 演算法的一個擴充套件。)然後,將同樣的想法遞迴地應用於匹配子序列左右兩邊的序列片段。這不會產生最小的編輯序列,但確實傾向於產生“看起來正確”的匹配結果。
計時: 基本的 Ratcliff-Obershelp 演算法在最壞情況下的時間複雜度是立方的,在預期情況下的時間複雜度是二次的。
SequenceMatcher
在最壞情況下的時間複雜度是二次的,而其預期情況下的行為以一種複雜的方式依賴於序列中有多少共同元素;最佳情況下的時間複雜度是線性的。自動垃圾啟發式演算法:
SequenceMatcher
支援一種啟發式演算法,可以自動將某些序列項視為垃圾。該啟發式演算法會計算每個獨立項在序列中出現的次數。如果一個項的重複出現(在第一次出現之後)佔序列的 1% 以上,並且序列長度至少為 200 項,則該項被標記為“熱門”,並在序列匹配時被視為垃圾。在建立SequenceMatcher
時,可以透過將autojunk
引數設定為False
來關閉此啟發式演算法。在 3.2 版本發生變更: 添加了 autojunk 形參。
- class difflib.Differ¶
這是一個用於比較文字行序列並生成人類可讀的差異或增量的類。Differ 使用
SequenceMatcher
來比較行序列,以及比較相似(近乎匹配)行內的字元序列。一個
Differ
差異的每一行都以一個雙字母程式碼開頭:程式碼
含義
'- '
序列 1 獨有的行
'+ '
序列 2 獨有的行
' '
兩個序列共有的行
'? '
兩個輸入序列中都不存在的行
以“
?
”開頭的行試圖引導視線關注行內差異,並且這些行在兩個輸入序列中都不存在。如果序列包含空格、製表符或換行符等空白字元,這些行可能會引起混淆。
- class difflib.HtmlDiff¶
這個類可以用來建立一個 HTML 表格(或包含該表格的完整 HTML 檔案),以並排、逐行的方式比較文字,並高亮顯示行間和行內的變化。該表格可以以完整模式或上下文差異模式生成。
這個類的建構函式是:
- __init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)¶
初始化
HtmlDiff
的例項。tabsize 是一個可選的關鍵字引數,用於指定製表符的間距,預設為
8
。wrapcolumn 是一個可選的關鍵字引數,用於指定折行和換行的列號,預設為
None
,表示不換行。linejunk 和 charjunk 是傳遞給
ndiff()
的可選關鍵字引數(HtmlDiff
使用它來生成並排的 HTML 差異)。有關引數的預設值和描述,請參閱ndiff()
的文件。
以下是公開方法:
- make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5, *, charset='utf-8')¶
比較 fromlines 和 tolines(字串列表),並返回一個字串,該字串是一個完整的 HTML 檔案,其中包含一個表格,逐行顯示差異,並高亮顯示行間和行內的變化。
fromdesc 和 todesc 是可選的關鍵字引數,用於指定 from/to 檔案列標題字串(兩者都預設為空字串)。
context 和 numlines 都是可選的關鍵字引數。當需要顯示上下文差異時,將 context 設定為
True
,否則預設為False
以顯示完整檔案。numlines 預設為5
。當 context 為True
時,numlines 控制差異高亮周圍的上下文行數。當 context 為False
時,在使用“下一個”超連結時,numlines 控制差異高亮前顯示的行數(設定為零將導致“下一個”超連結將下一個差異高亮放置在瀏覽器頂部,沒有任何前導上下文)。備註
fromdesc 和 todesc 被解釋為未轉義的 HTML,在從不受信任的來源接收輸入時應進行適當的轉義。
在 3.5 版本發生變更: 添加了 charset 僅關鍵字引數。HTML 文件的預設字元集從
'ISO-8859-1'
更改為'utf-8'
。
- make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)¶
比較 fromlines 和 tolines(字串列表),並返回一個字串,該字串是一個完整的 HTML 表格,逐行顯示差異,並高亮顯示行間和行內的變化。
此方法的引數與
make_file()
方法的引數相同。
- difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')¶
比較 a 和 b(字串列表);以上下文差異格式返回一個增量(一個生成增量行的生成器)。
上下文差異是一種緊湊的顯示方式,只顯示已更改的行以及幾行上下文。更改以前/後(before/after)的樣式顯示。上下文行數由 n 設定,預設為三。
預設情況下,差異控制行(帶有
***
或---
的行)建立時帶有尾隨換行符。這很有用,因為從io.IOBase.readlines()
建立的輸入會產生適合與io.IOBase.writelines()
一起使用的差異,因為輸入和輸出都有尾隨換行符。對於沒有尾隨換行符的輸入,將 lineterm 引數設定為
""
,以便輸出統一沒有換行符。上下文差異格式通常有一個用於檔名和修改時間的標頭。可以使用字串為 fromfile、tofile、fromfiledate 和 tofiledate 指定這些中的任何一個或所有。修改時間通常以 ISO 8601 格式表示。如果未指定,字串預設為空。
>>> import sys >>> from difflib import * >>> 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 是一個需要查詢緊密匹配的序列(通常是字串),而 possibilities 是一個用於與 word 匹配的序列列表(通常是字串列表)。
可選引數 n(預設為
3
)是要返回的最大緊密匹配數;n 必須大於0
。可選引數 cutoff(預設為
0.6
)是範圍 [0, 1] 內的浮點數。與 word 的相似度得分至少不低於該值的可能性將被忽略。在可能性中最好的(不超過 n 個)匹配項將以列表形式返回,按相似度得分排序,最相似的排在前面。
>>> 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)¶
比較 a 和 b(字串列表);返回一個
Differ
樣式的增量(一個生成增量行的生成器)。可選的關鍵字引數 linejunk 和 charjunk 是過濾函式(或
None
):linejunk:一個接受單個字串引數的函式,如果字串是垃圾則返回 true,否則返回 false。預設值為
None
。還有一個模組級函式IS_LINE_JUNK()
,它會過濾掉沒有可見字元的行,除了最多一個井號字元('#'
)——然而底層的SequenceMatcher
類會動態分析哪些行頻繁出現以構成噪聲,這通常比使用此函式效果更好。charjunk:一個接受單個字元(長度為 1 的字串)的函式,如果字元是垃圾則返回 true,否則返回 false。預設是模組級函式
IS_CHARACTER_JUNK()
,它會過濾掉空白字元(空格或製表符;將換行符包含在此中不是個好主意!)。>>> 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)¶
返回生成差異的兩個序列之一。
給定一個由
Differ.compare()
或ndiff()
生成的 sequence,提取源自檔案 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')¶
比較 a 和 b(字串列表);以統一差異格式返回一個增量(一個生成增量行的生成器)。
統一差異是一種緊湊的顯示方式,只顯示已更改的行以及幾行上下文。更改以內聯樣式顯示(而不是分開的 before/after 塊)。上下文行數由 n 設定,預設為三。
預設情況下,差異控制行(帶有
---
、+++
或@@
的行)建立時帶有尾隨換行符。這很有用,因為從io.IOBase.readlines()
建立的輸入會產生適合與io.IOBase.writelines()
一起使用的差異,因為輸入和輸出都有尾隨換行符。對於沒有尾隨換行符的輸入,將 lineterm 引數設定為
""
,以便輸出統一沒有換行符。統一差異格式通常有一個用於檔名和修改時間的標頭。可以使用字串為 fromfile、tofile、fromfiledate 和 tofiledate 指定這些中的任何一個或所有。修改時間通常以 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 比較 a 和 b(位元組物件列表);以 dfunc 返回的格式生成一個差異行序列(也是位元組)。dfunc 必須是一個可呼叫物件,通常是
unified_diff()
或context_diff()
。允許您比較具有未知或不一致編碼的資料。除 n 外的所有輸入都必須是位元組物件,而不是 str。它的工作原理是將所有輸入(除 n 外)無損地轉換為 str,然後呼叫
dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)
。然後將 dfunc 的輸出轉換回位元組,因此您收到的差異行與 a 和 b 具有相同的未知/不一致編碼。在 3.5 版本加入。
- difflib.IS_LINE_JUNK(line)¶
對可忽略的行返回
True
。如果 line 是空行或包含單個'#'
,則該行 line 是可忽略的,否則是不可忽略的。在舊版本中用作ndiff()
中引數 linejunk 的預設值。
- difflib.IS_CHARACTER_JUNK(ch)¶
對可忽略的字元返回
True
。如果字元 ch 是空格或製表符,則它是可忽略的,否則是不可忽略的。用作ndiff()
中引數 charjunk 的預設值。
參見
- 模式匹配:格式塔方法
John W. Ratcliff 和 D. E. Metzener 對類似演算法的討論。該文發表於 1988 年 7 月的《Dr. Dobb's Journal》。
SequenceMatcher 物件¶
SequenceMatcher
類有這個建構函式:
- class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)¶
可選引數 isjunk 必須是
None
(預設值)或一個單引數函式,該函式接受一個序列元素,當且僅當該元素是“垃圾”且應被忽略時返回 true。為 isjunk 傳遞None
等同於傳遞lambda x: False
;換句話說,不忽略任何元素。例如,傳遞:lambda x: x in " \t"
如果你正在將行作為字元序列進行比較,並且不希望在空格或硬製表符上進行同步。
可選引數 a 和 b 是要比較的序列;兩者都預設為空字串。兩個序列的元素都必須是可雜湊的。
可選引數 autojunk 可用於停用自動垃圾啟發式演算法。
在 3.2 版本發生變更: 添加了 autojunk 形參。
SequenceMatcher 物件有三個資料屬性:bjunk 是 b 中 isjunk 為
True
的元素集合;bpopular 是啟發式演算法認為熱門的非垃圾元素集合(如果未停用);b2j 是一個字典,將 b 中剩餘的元素對映到它們出現的位置列表。每當使用set_seqs()
或set_seq2()
重置 b 時,這三個屬性都會被重置。在 3.2 版新加: bjunk 和 bpopular 屬性。
SequenceMatcher
物件有以下方法:- set_seqs(a, b)¶
設定要比較的兩個序列。
SequenceMatcher
計算並快取關於第二個序列的詳細資訊,因此如果你想將一個序列與多個序列進行比較,請使用set_seq2()
設定一次常用序列,並對每個其他序列重複呼叫set_seq1()
。- set_seq1(a)¶
設定要比較的第一個序列。要比較的第二個序列不會改變。
- set_seq2(b)¶
設定要比較的第二個序列。要比較的第一個序列不會改變。
- find_longest_match(alo=0, ahi=None, blo=0, bhi=None)¶
在
a[alo:ahi]
和b[blo:bhi]
中查詢最長的匹配塊。如果 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
。對於所有滿足這些條件的(i', j', k')
,還會滿足附加條件k >= k'
,i <= i'
,以及如果i == i'
,則j <= j'
。換句話說,在所有最大的匹配塊中,返回一個在 a 中最早開始的,並且在所有這些在 a 中最早開始的最大匹配塊中,返回一個在 b 中最早開始的。>>> s = SequenceMatcher(None, " abcd", "abcd abcd") >>> s.find_longest_match(0, 5, 0, 9) Match(a=0, b=4, size=5)
如果提供了 isjunk,首先如上所述確定最長的匹配塊,但附加限制是塊中不能出現垃圾元素。然後,透過在兩側匹配(僅)垃圾元素,將該塊儘可能地擴充套件。因此,最終的塊永遠不會在垃圾上匹配,除非相同的垃圾恰好與一個有意義的匹配相鄰。
這裡是和前面一樣的例子,但將空格視為空白。這阻止了
' abcd'
直接與第二個序列末尾的' abcd'
匹配。相反,只有'abcd'
能夠匹配,並且匹配的是第二個序列中最左邊的'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)
。該方法返回一個命名元組
Match(a, b, size)
。在 3.9 版本發生變更: 添加了預設引數。
- get_matching_blocks()¶
返回描述不重疊匹配子序列的三元組列表。每個三元組的形式為
(i, j, n)
,表示a[i:i+n] == b[j:j+n]
。三元組在 i 和 j 上是單調遞增的。最後一個三元組是虛擬的,其值為
(len(a), len(b), 0)
。它是唯一一個n == 0
的三元組。如果(i, j, n)
和(i', j', n')
是列表中的相鄰三元組,且第二個不是列表中的最後一個三元組,則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 轉換為 b 的 5 元組列表。每個元組的形式為
(tag, i1, i2, j1, j2)
。第一個元組的i1 == j1 == 0
,其餘元組的 i1 等於前一個元組的 i2,同樣,j1 等於前一個元組的 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]
(子序列相等)。例如:
>>> 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)¶
返回一個最多包含 n 行上下文的分組生成器。
從
get_opcodes()
返回的組開始,此方法將較小的更改簇分離出來,並消除沒有更改的中間範圍。返回的組格式與
get_opcodes()
相同。
- ratio()¶
以 [0, 1] 範圍內的浮點數形式返回序列相似度的度量。
其中 T 是兩個序列中元素的總數,M 是匹配的數量,該值為 2.0*M / T。注意,如果序列相同,則為
1.0
,如果它們沒有任何共同之處,則為0.0
。如果尚未呼叫
get_matching_blocks()
或get_opcodes()
,則計算成本很高,在這種情況下,您可能需要先嚐試quick_ratio()
或real_quick_ratio()
以獲得上限。備註
注意:
ratio()
呼叫的結果可能取決於引數的順序。例如:>>> SequenceMatcher(None, 'tide', 'diet').ratio() 0.25 >>> SequenceMatcher(None, 'diet', 'tide').ratio() 0.5
這三個返回匹配字元與總字元比率的方法可能會因近似程度不同而給出不同的結果,儘管 quick_ratio()
和 real_quick_ratio()
總是至少與 ratio()
一樣大:
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0
SequenceMatcher 示例¶
此示例比較兩個字串,將空格視為“垃圾”:
>>> s = SequenceMatcher(lambda x: x == " ",
... "private Thread currentThread;",
... "private volatile Thread currentThread;")
ratio()
返回一個 [0, 1] 範圍內的浮點數,衡量序列的相似性。根據經驗,ratio()
值超過 0.6 表示序列非常匹配:
>>> print(round(s.ratio(), 3))
0.866
如果你只對序列匹配的位置感興趣,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()
返回的最後一個元組始終是一個虛擬元組,即 (len(a), len(b), 0)
,這是最後一個元組元素(匹配的元素數量)為 0
的唯一情況。
如果你想知道如何將第一個序列更改為第二個序列,請使用 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]
參見
此模組中的
get_close_matches()
函式展示瞭如何利用基於SequenceMatcher
的簡單程式碼來完成有用的工作。簡單版本控制配方 介紹了一個使用
SequenceMatcher
構建的小型應用程式。
Differ 物件¶
請注意,Differ
生成的增量並不聲稱是最小差異。相反,最小差異通常是反直覺的,因為它們會在任何可能的地方同步,有時是相隔 100 頁的偶然匹配。將同步點限制在連續匹配上保留了一些區域性性的概念,但偶爾會產生更長的差異。
Differ
類有這個建構函式:
- class difflib.Differ(linejunk=None, charjunk=None)
可選的關鍵字引數 linejunk 和 charjunk 是過濾函式(或
None
):linejunk:一個接受單個字串引數的函式,如果字串是垃圾,則返回 true。預設值為
None
,表示沒有行被認為是垃圾。charjunk:一個接受單個字元引數(長度為 1 的字串)的函式,如果字元是垃圾,則返回 true。預設值為
None
,表示沒有字元被認為是垃圾。這些垃圾過濾函式可以加快匹配速度以查詢差異,並且不會導致任何不同的行或字元被忽略。請閱讀
find_longest_match()
方法的 isjunk 引數的說明以獲取解釋。Differ
物件透過一個方法使用(生成差異):- compare(a, b)¶
比較兩個行序列,並生成差異(一個行序列)。
每個序列必須包含以換行符結尾的單個單行字串。這樣的序列可以從類檔案物件的
readlines()
方法獲得。生成的差異也由以換行符結尾的字串組成,可以透過類檔案物件的writelines()
方法直接列印。
Differ 示例¶
此示例比較兩個文字。首先,我們設定文字,即以換行符結尾的單個單行字串序列(此類序列也可以從類檔案物件的 readlines()
方法獲得):
>>> 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)
接下來,我們例項化一個 Differ 物件:
>>> d = Differ()
請注意,在例項化 Differ
物件時,我們可以傳遞函式來過濾掉行和字元的“垃圾”。有關詳細資訊,請參閱 Differ()
建構函式。
最後,我們比較兩者:
>>> result = list(d.compare(text1, text2))
result
是一個字串列表,所以我們來美化列印它:
>>> 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']
作為一個多行字串,它看起來像這樣:
>>> 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 的命令列介面¶
此示例展示瞭如何使用 difflib 建立一個類似 diff
的工具。
""" 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()
ndiff 示例¶
此示例展示瞭如何使用 difflib.ndiff()
。
"""ndiff [-q] file1 file2
or
ndiff (-r1 | -r2) < ndiff_output > file1_or_file2
Print a human-friendly file difference report to stdout. Both inter-
and intra-line differences are noted. In the second form, recreate file1
(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.
In the first form, if -q ("quiet") is not specified, the first two lines
of output are
-: file1
+: file2
Each remaining line begins with a two-letter code:
"- " line unique to file1
"+ " line unique to file2
" " line common to both files
"? " line not present in either input file
Lines beginning with "? " attempt to guide the eye to intraline
differences, and were not present in either input file. These lines can be
confusing if the source files contain tab characters.
The first file can be recovered by retaining only lines that begin with
" " or "- ", and deleting those 2-character prefixes; use ndiff with -r1.
The second file can be recovered similarly, but by retaining only " " and
"+ " lines; use ndiff with -r2; or, on Unix, the second file can be
recovered by piping the output through
sed -n '/^[+ ] /s/^..//p'
"""
__version__ = 1, 7, 0
import difflib, sys
def fail(msg):
out = sys.stderr.write
out(msg + "\n\n")
out(__doc__)
return 0
# open a file & return the file object; gripe and return 0 if it
# couldn't be opened
def fopen(fname):
try:
return open(fname)
except IOError as detail:
return fail("couldn't open " + fname + ": " + str(detail))
# open two files & spray the diff to stdout; return false iff a problem
def fcompare(f1name, f2name):
f1 = fopen(f1name)
f2 = fopen(f2name)
if not f1 or not f2:
return 0
a = f1.readlines(); f1.close()
b = f2.readlines(); f2.close()
for line in difflib.ndiff(a, b):
print(line, end=' ')
return 1
# crack args (sys.argv[1:] is normal) & compare;
# return false iff a problem
def main(args):
import getopt
try:
opts, args = getopt.getopt(args, "qr:")
except getopt.error as detail:
return fail(str(detail))
noisy = 1
qseen = rseen = 0
for opt, val in opts:
if opt == "-q":
qseen = 1
noisy = 0
elif opt == "-r":
rseen = 1
whichfile = val
if qseen and rseen:
return fail("can't specify both -q and -r")
if rseen:
if args:
return fail("no args allowed with -r option")
if whichfile in ("1", "2"):
restore(whichfile)
return 1
return fail("-r value must be 1 or 2")
if len(args) != 2:
return fail("need 2 filename args")
f1name, f2name = args
if noisy:
print('-:', f1name)
print('+:', f2name)
return fcompare(f1name, f2name)
# read ndiff output from stdin, and print file1 (which=='1') or
# file2 (which=='2') to stdout
def restore(which):
restored = difflib.restore(sys.stdin.readlines(), which)
sys.stdout.writelines(restored)
if __name__ == '__main__':
main(sys.argv[1:])