You are here:  Home » Python » difflib-计算增量的助手(12)Python语言(必读进阶学习教程)(参考资料)

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

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

时间:基本的Ratcliff-Obershelp算法在最坏情况下是立方时间,在预期情况下是二次时间。SequenceMatcher对于最坏情况,它是二次时间,并且具有预期情况行为,其依赖于序列共有多少元素的复杂方式; 最佳案例时间是线性的。

自动垃圾启发式: SequenceMatcher支持启发式,可自动将某些序列项视为垃圾。启发式计算每个单独项目在序列中出现的次数。如果项目的重复项(在第一项之后)占序列的1%以上且序列长度至少为200项,则此项目被标记为“热门”并且为了序列匹配而被视为垃圾。可以通过在创建时设置autojunk参数来关闭此启发式。FalseSequenceMatcher

新版本3.2:autojunk参数。

difflib.Differ
这是一个用于比较文本行序列并产生人类可读差异或增量的类。Differ使用SequenceMatcher 两者来比较行的序列,并比较相似(近匹配)行内的字符序列。

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

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

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

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

这个类的构造函数是:

__init__tabsize = 8wrapcolumn = Nonelinejunk = Nonecharjunk = IS_CHARACTER_JUNK 
初始化实例HtmlDiff

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

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

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

以下方法是公开的:

make_filefromlinestolinesfromdesc =”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_tablefromlinestolinesfromdesc =”todesc =”context = Falsenumlines = 5 
比较fromlinestolines(字符串列表)并返回一个字符串,该字符串是一个完整的HTML表格,显示逐行差异,突出显示行间和行内更改。

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

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

difflib.context_diffabfromfile =”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单词可能性n = 3截止= 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.ndiffablinejunk = 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序列哪个
返回生成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_diffabfromfile =”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_bytesdfuncabfromfile = 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
对于可忽略的行返回true。线线是可忽略的,如果线为空或包含一个单一的'#',否则就不是忽略。作为参数的默认linejunkndiff()旧版本中。
difflib.IS_CHARACTER_JUNKch 
对于可忽略的字符,返回true。如果ch 是空格或制表符,则字符ch是可忽略的,否则它是不可忽略的。作为参数的默认charjunk在。ndiff()

也可以看看

模式匹配:格式塔方法
讨论John W. Ratcliff和DE Metzener的类似算法。这发表在1988年7月的Dobb博士期刊上。

SequenceMatcher对象

SequenceMatcher班有这样的构造函数:

class difflib.SequenceMatcherisjunk = 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_matchaloahiblobhi 
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_opcodesn = 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.Differlinejunk = Nonecharjunk = None 
可选关键字参数linejunkcharjunk用于过滤函数(或None):

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

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

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

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

comparea
比较两个行序列,并生成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()