该模块提供用于比较序列的类和函数。例如,它可以用于比较文件,并且可以生成各种格式的差异信息,包括 HTML 和上下文以及统一差异。要比较目录和文件,另请参阅filecmp模块。

classdifflib.SequenceMatcher
这是一个灵活的类,用于比较任何类型的序列对,只要序列元素是可散列的。基本算法早于 Ratcliff 和 Obershelp 在 1980 年代后期以双曲线名称“格式塔模式匹配”发布的算法,并且比它更高级一些。这个想法是找到不包含“垃圾”元素的最长连续匹配子序列;这些“垃圾”元素在某种意义上是无趣的,例如空行或空格。(处理垃圾是 Ratcliff 和 Obershelp 算法的扩展。)然后将相同的想法递归地应用于匹配子序列左侧和右侧的序列片段。这不会产生最少的编辑序列,但会产生对人们“看起来正确”的匹配。

时序:基本的 Ratcliff-Obershelp 算法在最坏情况下是三次时间,在预期情况下是二次时间。SequenceMatcher是最坏情况的二次时间,并且预期情况的行为以复杂的方式取决于序列有多少共同元素;最佳情况时间是线性的。

自动垃圾启发式: SequenceMatcher支持自动将某些序列项视为垃圾的启发式。启发式计算每个单独项目在序列中出现的次数。如果一个项目的重复项(在第一个之后)占序列的 1% 以上,并且序列长度至少为 200 个项目,则该项目被标记为“流行”并被视为垃圾,以进行序列匹配。可以通过autojunkFalse创建SequenceMatcher.

3.2 新版功能: autojunk参数

classdifflib.Differ
这是一个用于比较文本行序列并产生人类可读差异或增量的类。DifferSequenceMatcher 既用于比较行序列,也用于比较相似(接近匹配)行中的字符序列。

delta的每一行都Differ以两个字母的代码开头:

含义
'- ' 序列1独有的行
'+ ' 序列2独有的行
'  ' 两个序列共有的线
'? ' 两个输入序列中都不存在该行

以’ ?‘ 开头的行试图引导眼睛注意到内部差异,并且在任一输入序列中都不存在。如果序列包含制表符,这些行可能会造成混淆。

classdifflib.HtmlDiff
此类可用于创建HTML表(或包含表的完整HTML文件),并逐行显示文本与行间和行内更改突出显示的逐行比较。该表可以在完全或上下文差异模式下生成。

这个类的构造函数是:

__init__(tabsize=8wrapcolumn=Nonelinejunk=Nonecharjunk=IS_CHARACTER_JUNK)
初始化实例HtmlDiff

tabsize是一个可选的关键字参数,用于指定制表位间距和默认值8

wrapcolumn是一个可选的关键字,用于指定拆分和包装行的列号,默认为None未包装行的位置。

linejunkcharjunk是传入的可选关键字参数ndiff() (用于HtmlDiff生成并排的HTML差异)。有关ndiff()参数默认值和说明,请参阅 文档。

以下方法是公开的:

make_file(fromlinestolinesfromdesc=”todesc=”context=Falsenumlines=5*charset=’utf-8′)
比较fromlinestolines(字符串列表)并返回一个字符串,该字符串是一个完整的HTML文件,其中包含一个表格,显示逐行差异,突出显示行间和行内更改。

fromdesctodesc是可选的关键字参数,用于指定从/到文件列标题字符串(默认为空字符串)。

contextnumlines都是可选的关键字参数。设置方面,以 True当背景的差异将被显示,否则默认为False可显示完整的文件。numlines默认为5。当上下文 为True numlines时,控制围绕差异突出显示的上下文行数。当上下文False numlines时,控制使用“下一个”超链​​接时差异高亮显示之前显示的行数(设置为零会导致“下一个”超链​​接将下一个差异高亮显示在浏览器顶部而没有任何前导上下文)。

版本3.5中已更改:添加了charset仅关键字参数。HTML文档的默认字符集从更改'ISO-8859-1''utf-8'

make_table(fromlinestolinesfromdesc=”todesc=”context=Falsenumlines=5)
比较fromlinestolines(字符串列表)并返回一个字符串,该字符串是一个完整的HTML表格,显示逐行差异,突出显示行间和行内更改。

此方法的参数与方法的参数相同make_file() 。

Tools/scripts/diff.py 是这个类的命令行前端,并包含一个很好的使用示例。

difflib.context_diff(abfromfile=”tofile=”fromfiledate=”tofiledate=”n=3lineterm=’\n’)
比较ab(字符串列表); 以上下文diff格式返回delta(生成delta行的生成器)。

上下文差异是一种紧凑的方式,只显示已更改的行加上几行上下文。更改以前/后样式显示。上下文行的数量由n设置,默认为3。

默认情况下,diff控制线(带***或的那些---)是使用尾随换行符创建的。这是有帮助的,因此由于输入和输出都具有尾随换行符io.IOBase.readlines(),因此io.IOBase.writelines()从输入和输出中产生的输入创建的输入 适合使用 。

对于没有尾随换行符的输入,请将lineterm参数设置为以 ""使输出统一为newline free。

上下文差异格式通常具有文件名和修改时间的标头。可以使用fromfile, tofilefromfiledatetofiledate的字符串指定任何或所有这些。修改时间通常以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(wordpossibilitiesn=3cutoff=0.6)
返回最佳“足够好”的比赛列表。 word是需要密切匹配的序列(通常是字符串),并且可能性是与匹配的序列列表(通常是字符串列表)。

可选参数n(默认值3)是要返回的最大匹配数; n必须大于0

可选参数cutoff(默认值0.6)是[0,1]范围内的浮点数。不会得到至少与单词类似的得分的可能性被忽略。

可能性中的最佳(不超过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(ablinejunk=Nonecharjunk=IS_CHARACTER_JUNK)
比较ab(字符串列表); 返回a- Differstyle delta(生成三角形线的生成器)。

可选的关键字参数linejunkcharjunk是过滤功能(或None):

linejunk:接受单个字符串参数的函数,如果字符串是垃圾则返回true,否则返回false。默认是None。还有一个模块级函数IS_LINE_JUNK(),它过滤掉没有可见字符的行,除了最多一磅字符('#') – 但是底层SequenceMatcher类对动态分析哪些行频繁构成噪声,这通常有效比使用这个功能更好。

charjunk:接受字符(长度为1的字符串)的函数,如果字符是垃圾则返回,否则返回false。默认是模块级函数IS_CHARACTER_JUNK(),它过滤掉空格字符(空格或制表符;在此包含换行符是个坏主意!)。

Tools/scripts/ndiff.py 是此函数的命令行前端。

>>> 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(sequencewhich)
返回生成delta的两个序列之一。

给定一个序列通过产生Differ.compare()ndiff()从文件1或2(参数始发,提取线其中),剥离线前缀。

例:

>>> 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(abfromfile=”tofile=”fromfiledate=”tofiledate=”n=3lineterm=’\n’)
比较ab(字符串列表); 以统一的diff格式返回delta(生成delta行的生成器)。

统一差异是一种紧凑的方式,只显示已更改的行加上几行上下文。更改以内联样式显示(而不是在块之前/之后单独)。上下文行的数量由n设置,默认为3。

默认情况下,差异控制线(那些---+++@@)与尾部换行符创建。这是有帮助的,因此由于输入和输出都具有尾随换行符io.IOBase.readlines(),因此io.IOBase.writelines()从输入和输出中产生的输入创建的输入适合使用 。

对于没有尾随换行符的输入,请将lineterm参数设置为以 ""使输出统一为newline free。

上下文差异格式通常具有文件名和修改时间的标头。可以使用fromfile, tofilefromfiledatetofiledate的字符串指定任何或所有这些。修改时间通常以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(dfuncabfromfile=b”tofile=b”fromfiledate=b”tofiledate=b”n=3lineterm=b’\n’)
使用dfunc比较ab(字节对象列表); 以dfunc返回的格式生成一系列delta行(也是字节)。 dfunc必须是可调用的,通常是或者 。unified_diff()context_diff()

允许您比较具有未知或不一致编码的数据。除n之外的所有输入必须是字节对象,而不是str。通过无损地将所有输入(除了n)转换为str和调用来工作。然后将dfunc的输出 转换回字节,因此您收到的delta行与ab具有相同的未知/不一致编码。dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)

版本3.5中的新功能。

difflib.IS_LINE_JUNK(line)
对于可忽略的行返回true。线线是可忽略的,如果线为空或包含一个单一的'#',否则就不是忽略。作为参数的默认linejunkndiff()旧版本中。
difflib.IS_CHARACTER_JUNK(ch)
对可忽略的字符返回 True。 如果 ch 是空格或制表符,则字符 ch 可忽略,否则不可忽略。 在 ndiff() 中用作参数 charjunk 的默认值。

SequenceMatcher对象

SequenceMatcher班有这样的构造函数:

class difflib.SequenceMatcher(isjunk=Nonea=”b=”autojunk=True)
可选参数isjunk必须是None(缺省值)或带有sequence元素的单参数函数,当且仅当元素是“垃圾”并且应该被忽略时才返回true。通过Noneisjunk相当于通过; 换句话说,没有元素被忽略。例如,通过:lambda x: 0

lambda x: x in " \t"

如果你将线条比较为字符序列,并且不想在空白或硬标签上进行同步。

可选参数ab是要比较的序列; 两者都默认为空字符串。两个序列的元素必须是可清除的

可选参数autojunk可用于禁用自动垃圾启发式。

新版本3.2:autojunk参数。

SequenceMatcher对象获得三个数据属性:bjunkb的元素集,isjunkTruebpopular是启发式考虑的非垃圾元素集(如果它没有被禁用); b2j是一个字典,它将b的剩余元素映射到它们出现的位置列表。每当用或复位b时,所有三个都复位。set_seqs()set_seq2()

新版本3.2:bjunkbpopular属性。

SequenceMatcher 对象具有以下方法:

set_seqsa
设置要比较的两个序列。

SequenceMatcher计算和缓存有关第二个序列的详细信息,因此如果要将一个序列与多个序列进行比较,请使用set_seq2()一次设置常用序列并set_seq1()重复调用,每个其他序列一次。

set_seq1
设置要比较的第一个序列。要比较的第二个序列没有改变。
set_seq2
设置要比较的第二个序列。要比较的第一个序列没有改变。
find_longest_match(aloahiblobhi)
a[alo:ahi]和中找到最长的匹配块b[blo:bhi]

如果isjunk省略或Nonefind_longest_match()返回 ,使得等于,其中和。对于所有符合这些条件的,额外的条件,和如果,也达到了。换言之,所有最大匹配块,返回一个启动在最早一个,以及所有开始最早的那些最大匹配块的一个,返回在开始最早的一个b(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'

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

如果提供了isjunk,则首先确定最长匹配块,如上所述,但是具有额外限制,即块中不出现垃圾元素。然后通过匹配(仅)两侧的垃圾元素尽可能地扩展该块。因此,结果块永远不会在垃圾上匹配,除非相同的垃圾恰好与有趣的匹配相邻。

这是与以前相同的例子,但考虑到空白是垃圾。这可以防止直接匹配第二序列的尾端。相反,只有can匹配,并匹配第二个序列中最左边的:' 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)

get_matching_blocks()
返回描述非重叠匹配子序列的三元组列表。每个三元组都是形式,意思是。三元组在ij中单调递增。(i,j, n)a[i:i+n] == b[j:j+n]

最后一个三元组是一个虚拟,并具有该值。这是唯一的三倍。如果 列表中是和相邻的三元组,而第二个不是列表中的最后三元组,那么或; 换句话说,相邻的三元组总是描述不相邻的相等的块。(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()
返回5元组的返回列表,描述如何将a转换为b。每个元组都是这种形式。第一个元组有,并且剩余元组的i1等于前一个元组的i2,同样,j1等于前一个j2(tag, i1, i2, j1, j2)i1 == j1 == 0

标签值是字符串,这些含义:

含义
'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()首先获得上限。

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]

也可以看看

  • get_close_matches()此模块中的函数显示了如何构建简单的代码SequenceMatcher可用于执行有用的工作。
  • 用于构建的小型应用程序的简单版本控制配方SequenceMatcher

不同的对象

请注意,Differ生成的增量不会声称是最小 差异。相反,最小差异通常是违反直觉的,因为它们可能在任何可能的地方同步,有时相距100页。将同步点限制为连续匹配保留了一些局部性概念,偶尔会产生较长的差异。

Differ班有这样的构造函数:

class difflib.Differ(linejunk=Nonecharjunk=None)
可选关键字参数linejunkcharjunk用于过滤函数(或None):

linejunk:接受单个字符串参数的函数,如果字符串是垃圾,则返回true。默认值是None,意味着没有行被视为垃圾。

charjunk:接受单个字符参数(长度为1的字符串)的函数,如果字符是垃圾,则返回true。默认值是None,意味着没有字符被视为垃圾。

这些垃圾过滤功能可加快匹配以查找差异,并且不会导致忽略任何不同的行或字符。阅读find_longest_match()方法的isjunk 参数的说明以获得解释。

Differ 通过单个方法使用对象(生成的增量):

compare(ab)
比较两个行序列,并生成delta(一系列行)。

每个序列必须包含以换行符结尾的单个单行字符串。可以从readlines()类文件对象的方法获得这样的序列 。生成的增量也包括换行符终止的字符串,可以通过类writelines()文件对象的方法按原样打印。

不同的例子

此示例比较两个文本。首先,我们设置文本,以换行符结尾的单个单行字符串的序列(这样的序列也可以从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实用程序。它也包含在Python源代码发行版中 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()