difflib — 用於計算差異的輔助工具

原始碼: Lib/difflib.py


此模組提供了用於比較序列的類和函式。它可用於例如比較檔案,並能以各種格式生成有關檔案差異的資訊,包括 HTML 和上下文以及統一差異。要比較目錄和檔案,另請參閱 filecmp 模組。

class difflib.SequenceMatcher

這是一個靈活的類,用於比較任何型別的序列對,只要序列元素是可雜湊的。 基本演算法早於 1980 年代後期 Ratcliff 和 Obershelp 以誇張的名稱“格式塔模式匹配”發表的演算法,並且比它稍複雜一些。其思想是找到不包含“垃圾”元素的最長連續匹配子序列;這些“垃圾”元素在某種意義上是不感興趣的,例如空白行或空格。(處理垃圾是對 Ratcliff 和 Obershelp 演算法的擴充套件。)然後將相同的想法遞迴地應用於匹配子序列的左側和右側的序列片段。這不會產生最小編輯序列,但往往會產生“看起來對”人的匹配。

時間:基本的 Ratcliff-Obershelp 演算法在最壞的情況下是立方時間,在預期情況下是平方時間。SequenceMatcher 在最壞的情況下是平方時間,並且其預期情況的行為以複雜的方式依賴於序列有多少個共同的元素;最佳情況時間是線性的。

自動垃圾啟發式:SequenceMatcher 支援一種啟發式演算法,該演算法自動將某些序列項視為垃圾。該啟發式演算法計算每個單獨的項在序列中出現的次數。如果一個項的重複項(在第一個之後)佔序列的 1% 以上,並且該序列至少有 200 項長,則該項被標記為“受歡迎”,並且出於序列匹配的目的而被視為垃圾。可以透過在建立 SequenceMatcher 時將 autojunk 引數設定為 False 來關閉此啟發式演算法。

在 3.2 版本中更改: 添加了 *autojunk* 引數。

class difflib.Differ

這是一個用於比較文字行序列並生成人類可讀的差異或增量的類。Differ 使用 SequenceMatcher 來比較行序列,並比較相似(近似匹配)行中的字元序列。

Differ 增量的每一行都以一個雙字母程式碼開頭

程式碼

含義

'- '

'+ '

'+ '

'- '

' ' ' '

兩個序列共有的行

'? '

兩個輸入序列中都不存在的行

以“?”開頭的行嘗試引導眼睛注意行內差異,並且不存在於任何一個輸入序列中。如果序列包含空格字元(例如空格、製表符或換行符),這些行可能會令人困惑。

class difflib.HtmlDiff

此類可用於建立 HTML 表格(或包含表格的完整 HTML 檔案),以並排方式逐行顯示文字比較結果,並突出顯示行間和行內更改。該表格可以在完整或上下文差異模式下生成。

此類的建構函式是

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

初始化 HtmlDiff 的例項。

tabsize 是一個可選的關鍵字引數,用於指定製表位間距,預設為 8

wrapcolumn 是一個可選的關鍵字,用於指定行的斷開和換行的列號,預設為 None,表示行不換行。

linejunkcharjunk 是傳遞給 ndiff() 的可選關鍵字引數(由 HtmlDiff 用於生成並排 HTML 差異)。 有關引數預設值和說明,請參見 ndiff() 文件。

以下方法是公共的

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

比較 fromlinestolines(字串列表),並返回一個字串,該字串是一個完整的 HTML 檔案,其中包含一個表格,顯示逐行差異,並突出顯示行間和行內更改。

fromdesctodesc 是可選的關鍵字引數,用於指定來自/到檔案列標題字串(兩者預設為空字串)。

contextnumlines 都是可選的關鍵字引數。 當要顯示上下文差異時,將 context 設定為 True,否則預設為 False 以顯示完整的檔案。 numlines 預設為 5。 當 contextTrue 時,numlines 控制圍繞差異突出顯示的上下文行數。當 contextFalse 時,在使用“下一個”超連結時,numlines 控制在差異突出顯示之前顯示的行數(設定為零會導致“下一個”超連結將下一個差異突出顯示放置在瀏覽器頂部,而沒有任何前導上下文)。

注意

fromdesctodesc 被解釋為未轉義的 HTML,並且在接收來自不受信任的來源的輸入時應正確轉義。

在 3.5 版本中更改: 添加了僅關鍵字的 charset 引數。 HTML 文件的預設字元集從 'ISO-8859-1' 更改為 'utf-8'

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

比較 fromlinestolines (字串列表),並返回一個字串,該字串是一個完整的 HTML 表格,逐行顯示差異,並突出顯示行間和行內更改。

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

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

比較 ab (字串列表);以上下文差異格式返回增量(一個 生成器,生成增量行)。

上下文差異是一種緊湊的方式,只顯示已更改的行以及一些上下文行。更改以前後樣式顯示。上下文行的數量由 n 設定,預設為 3。

預設情況下,差異控制行(帶有 ***--- 的行)會建立一個尾隨換行符。這很有用,這樣從 io.IOBase.readlines() 建立的輸入將產生適合與 io.IOBase.writelines() 一起使用的差異,因為輸入和輸出都有尾隨換行符。

對於沒有尾隨換行符的輸入,請將 lineterm 引數設定為 "",以便輸出統一不包含換行符。

上下文差異格式通常具有檔名和修改時間的標題。可以使用 fromfiletofilefromfiledatetofiledate 的字串來指定其中任何一個或全部。修改時間通常以 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)

比較 ab (字串列表);返回一個 Differ 樣式的增量(一個 生成器,生成增量行)。

可選關鍵字引數 linejunkcharjunk 是過濾函式(或 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')

比較 ab (字串列表);以統一差異格式返回增量(一個 生成器,生成增量行)。

統一差異是一種緊湊的方式,只顯示已更改的行以及一些上下文行。更改以內聯樣式顯示(而不是單獨的前後塊)。上下文行的數量由 n 設定,預設為 3。

預設情況下,差異控制行(帶有 ---+++@@ 的行)會建立一個尾隨換行符。這很有用,這樣從 io.IOBase.readlines() 建立的輸入將產生適合與 io.IOBase.writelines() 一起使用的差異,因為輸入和輸出都有尾隨換行符。

對於沒有尾隨換行符的輸入,請將 lineterm 引數設定為 "",以便輸出統一不包含換行符。

統一差異格式通常包含用於檔名和修改時間的頭部資訊。可以使用字串為 fromfiletofilefromfiledatetofiledate 指定其中的任何或全部。修改時間通常以 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(位元組物件列表);以 dfunc 返回的格式生成一系列增量行(也是位元組)。dfunc 必須是可呼叫的,通常是 unified_diff()context_diff()

允許您比較具有未知或不一致編碼的資料。除 n 之外的所有輸入都必須是位元組物件,而不是 str。透過無損地將所有輸入(除 n 之外)轉換為 str,然後呼叫 dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm) 來實現。dfunc 的輸出隨後被轉換回位元組,因此您收到的增量行與 ab 具有相同的未知/不一致編碼。

3.5 版本新增。

difflib.IS_LINE_JUNK(line)

對於可忽略的行返回 True。如果 line 是空行或包含單個 '#',則 line 是可忽略的,否則是不可忽略的。在較舊版本中,用作 ndiff() 中引數 linejunk 的預設值。

difflib.IS_CHARACTER_JUNK(ch)

對於可忽略的字元返回 True。如果字元 ch 是空格或製表符,則 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"

如果您將行作為字元序列進行比較,並且不希望在空格或硬製表符上同步。

可選引數 ab 是要比較的序列;兩者都預設為空字串。兩個序列的元素都必須是 可雜湊的

可選引數 autojunk 可用於停用自動垃圾啟發式方法。

在 3.2 版本中更改: 添加了 *autojunk* 引數。

SequenceMatcher 物件具有三個資料屬性:bjunkbisjunkTrue 的元素集合;bpopular 是啟發式方法(如果未停用)認為流行的非垃圾元素集合;b2j 是將 b 的其餘元素對映到它們出現的位置列表的字典。每當使用 set_seqs()set_seq2() 重置 b 時,所有三個屬性都會被重置。

3.2 版本新增: bjunkbpopular 屬性。

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 或為 Nonefind_longest_match() 返回 (i, j, k),使得 a[i:i+k] 等於 b[j:j+k],其中 alo <= i <= i+k <= ahiblo <= 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()

返回 ratio() 的一個相對較快的上限。

real_quick_ratio()

返回 ratio() 的一個非常快的上限。

返回匹配字元與總字元之比的這三種方法,由於近似程度不同,可能會給出不同的結果,但 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]

另請參閱

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:])