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