7.4. difflibHelpers for computing deltas

2.1 版中的新增。

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

class difflib.SequenceMatcher

这是一个灵活的类,用于比较的序列的任何类型,对,只要序列元素是hashable基本算法早,并为有点花哨的算法出版在 80 年代末由拉特克利夫和 Obershelp 下双曲型名称"格式塔模式匹配法"。这个想法是找到最长连续匹配的子序列,不包含任何"垃圾"的元素 (拉特克利夫和 Obershelp 算法并不能解决垃圾)。同样的想法然后应用递归序列的左侧和右侧的匹配序列片段。这不会产生最小的编辑序列,但却倾向于产生"眺望权"的比赛的人。

时间:基本的拉特克利夫 Obershelp 算法在最坏的情况和二次时间在预期的情况下是立方次。 SequenceMatcher是为最坏情况下的二次和具有预期案例行为在一个复杂的方式取决于多少个元素的序列有共同之处 ;最佳案例时间是线性的。

自动垃圾的启发式算法: SequenceMatcher支持会自动将某些序列项目视为垃圾的启发式算法。启发式计数每个单独项目序列中出现了多少次。如果超过 1%的序列和序列项目 (后的第一个) 的重复帐户是至少 200 项这么长,这一项目被标记为"流行",被视为为序列匹配的垃圾。这启发式算法可以通过将autojunk参数设置为False ,创建SequenceMatcher时关闭。

新版本 2.7.1 中的:Autojunk参数。

class difflib.Differ

这是文本的一类比较序列、 行和生产人类可读的差异或增量。Differ使用SequenceMatcher ,比较序列的线条,和比较的类似 (接近匹配) 行中的字符序列。

每一行开头以两个不同的字母符号开头:

Code Meaning
'- ' line unique to sequence 1
'+ ' line unique to sequence 2
'  ' line common to both sequences
'? ' line not present in either input sequence

以 '?' 开头的行试图高亮每行内的分歧,引导眼睛和任一输入序列中不在场。这些行可以是混乱的如果序列包含制表符字符。

class difflib.HtmlDiff

此类可用于创建一个 HTML 表 (或一个完整的 HTML 文件,包含表) 显示文本肩并肩,逐行比较与跨线和内部线更改突出显示。可以在任一完整或上下文的不同模式下生成的表。

此类的构造函数是:

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

初始化HtmlDiff的实例。

tabsize是可选的关键字参数来指定选项卡停止间距和默认值为8

wrapcolumn是一个可选的关键字,以指定在哪里行的破裂和包裹,默认值为不换行的列数。

linejunkcharjunk是可选的关键字参数传递到ndiff() (使用HtmlDiff来生成并排 HTML 的分歧)。请参阅ndiff()参数默认值和说明文档。

下列方法是公共的:

make_file(fromlines, tolines [, fromdesc][, todesc][, context][, numlines])

Fromlinestolines (字符串列表) 进行了比较,并返回一个字符串,它是一个完整包含显示一行一行的差异与跨线和内线条进行更改突出显示的表的 HTML 文件。

fromdesctodesc是可选的关键字参数来指定从/到文件列标题字符串 (这两个默认值为空字符串)。

上下文numlines是两个可选的关键字参数。设置上下文True语境差异时要显示其他默认值为False以便显示完整的文件。numlines默认值为5上下文真正numlines控件围拢差异的上下文行数会突出显示。当为False上下文 numlines控制的差异突出显示之前使用"下一步"的超链接时显示的行数 (设置为零将导致的"下一步"的超链接,在没有任何领先的情况下浏览器的顶部放置下一个差异突出显示)。

make_table(fromlines, tolines [, fromdesc][, todesc][, context][, numlines])

Fromlinestolines (字符串列表) 进行了比较,并返回一个字符串,它是完整 HTML 表显示一行一行的差异与跨线和内线条进行更改突出显示。

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

Tools/scripts/diff.py是命令行前端向此类和包含它的使用很好的例子。

在 2.4 版本新。

difflib.context_diff(a, b[, fromfile][, tofile][, fromfiledate][, tofiledate][, n][, lineterm])

比较ab (字符串列表) ;在上下文 diff 格式返回一个三角洲 (生成器生成三角行)。

上下文差别是上下文的以紧凑的方式显示已更改的行,再加上的几行。更改显示在前/后风格。n向三个默认设置的上下文行数。

默认情况下,创建尾随换行符 diff 控制线 (那些*-)。这是很有帮助,以便投入从file.readlines()中适合的差异的结果创建以来这两个输入都用file.writelines()和产出有尾随换行符。

For inputs that do not have trailing newlines, set the lineterm argument to "" so that the output will be uniformly newline free.

上下文 diff 格式通常有一个标题为文件名和修改时间。使用字符串为fromfiletofilefromfiledatetofiledate可能指定任何或所有这些。修改时间通常是使用 ISO 8601 格式表示的。如果未指定,字符串默认为空格。

>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> for line in context_diff(s1, s2, fromfile='before.py', tofile='after.py'):
...     sys.stdout.write(line)  
*** before.py
--- after.py
***************
*** 1,4 ****
! bacon
! eggs
! ham
  guido
--- 1,4 ----
! python
! eggy
! hamster
  guido

更详细的示例,请参阅命令行界面来 difflib

新版本 2.3。

difflib.get_close_matches(word, possibilities[, n][, cutoff])

返回一个最佳的"足够好"匹配列表。是一个序列的相接近的匹配是期望的 (通常是一个字符串),和可能性是序列,用以匹配单词(通常为字符串的列表) 的列表。

可选参数n (默认3) 是密切匹配项返回 ; 的最大数量n必须大于0

可选参数截止(默认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('apple', keyword.kwlist)
[]
>>> get_close_matches('accept', keyword.kwlist)
['except']
difflib.ndiff(a, b[, linejunk][, charjunk])

比较ab (字符串列表) ;返回的不同-风格三角洲 (生成器生成三角行)。

可选的关键字参数linejunkcharjunk是过滤函数 (或没有):

linejunk: 一个函数,接受单个字符串参数,并返回 true,如果字符串是垃圾或假如果不。默认值是 () 入手,Python 2.3。在那之前,默认值是模块级函数IS_LINE_JUNK(),后者可筛选出没有可见的字符,除了在最一磅字符 ('#') 的行。一样的 Python 2.3 基础的SequenceMatcher类动态分析线的频繁,就构成噪音,而这通常比前 2.3 默认工作。

charjunk: 不接受一个字符 (长度为 1 的字符串),并返回如果字符是垃圾或假如果一个函数。默认值是模块级函数IS_CHARACTER_JUNK(),后者可筛选出空白字符 (一片空白或选项卡 ; 注意: 糟糕的主意在这中包含换行符 !)。

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

>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(1),
...              'ore\ntree\nemu\n'.splitlines(1))
>>> print ''.join(diff),
- one
?  ^
+ ore
?  ^
- two
- three
?  -
+ tree
+ emu
difflib.restore(sequence, which)

返回两个序列生成一个三角洲之一。

给定一个显序列产生的Differ.compare()ndiff(),提取线来自文件,1 或 2 (参数),脱线的前缀。

示例:

>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(1),
...              'ore\ntree\nemu\n'.splitlines(1))
>>> diff = list(diff) # materialize the generated delta into a list
>>> print ''.join(restore(diff, 1)),
one
two
three
>>> print ''.join(restore(diff, 2)),
ore
tree
emu
difflib.unified_diff(a, b[, fromfile][, tofile][, fromfiledate][, tofiledate][, n][, lineterm])

比较ab (字符串列表) ;在统一的 diff 格式返回一个三角洲 (生成器生成三角行)。

统一的差别是上下文的以紧凑的方式显示已更改的行,再加上的几行。(而不是单独前/后块) 的内联样式中,列载的变动。n向三个默认设置的上下文行数。

默认情况下,比较控制线 (那些- +++@ @) 创建尾随换行符。这是很有帮助,以便投入从file.readlines()中适合的差异的结果创建以来这两个输入都用file.writelines()和产出有尾随换行符。

For inputs that do not have trailing newlines, set the lineterm argument to "" so that the output will be uniformly newline free.

上下文 diff 格式通常有一个标题为文件名和修改时间。使用字符串为fromfiletofilefromfiledatetofiledate可能指定任何或所有这些。修改时间通常是使用 ISO 8601 格式表示的。如果未指定,字符串默认为空格。

>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> for line in unified_diff(s1, s2, fromfile='before.py', tofile='after.py'):
...     sys.stdout.write(line)   
--- before.py
+++ after.py
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
 guido

更详细的示例,请参阅命令行界面来 difflib

新版本 2.3。

difflib.IS_LINE_JUNK(line)

返回 true 可忽略行。线线是可忽略的如果线为空或包含一个单一的'#',否则就不是可忽略。用作参数linejunkndiff()之前 Python 2.3 中的默认值。

difflib.IS_CHARACTER_JUNK(ch)

返回 true 可忽略的字符。字符ch是可忽略的如果ch是空格或制表符),否则就不是可忽略。用作参数charjunkndiff()的默认值。

请参见

Pattern Matching: The Gestalt Approach
Discussion of a similar algorithm by John W. Ratcliff and D. E. Metzener. This was published in Dr. Dobb’s Journal in July, 1988.

7.4.1. SequenceMatcher Objects

SequenceMatcher类具有此构造函数:

class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)

可选参数isjunk必须是(默认值) 或一个单参数的函数,采用序列的元素,并返回 true,当且仅当该元素是"垃圾",并且应该忽略。传递Noneisjunk等于传递lambda x: 0换句话说,没有元素将被忽略。例如,将传递:

lambda x: x in " \t"

如果您比较行作为序列的字符,并且不希望以同步上空白或硬制表符。

可选参数b是序列进行比较 ;两者都默认为空字符串。这两个序列的元素必须是hashable

可选参数autojunk可以用来禁用自动垃圾启发式算法。

新版本 2.7.1 中的:Autojunk参数。

SequenceMatcher对象具有以下方法:

set_seqs(a, b)

设置两个序列进行比较。

SequenceMatcher计算和缓存有关第二个序列的详细的信息,所以如果你想要比较反对多个序列的一个序列,使用set_seq2()来一次设置常用的序列和set_seq1()反复,一次为每个调用的其他两个序列。

set_seq1(a)

设置要比较的第一个序列。要比较的第二个序列不会更改。

set_seq2(b)

设置要比较的第二个序列。要比较的第一个序列不会更改。

find_longest_match(alo, ahi, blo, bhi)

[Alo:ahi][blo:bhi] b中找到最长匹配块。

如果省略了isjunkfind_longest_match() 没有返回(我, j, k)这样[i:i + k]等于b [j:j + k],在那里alo < = < = 第一 + k < = ahi血压 < = j < = j + k < = bhi所有(我 ', j', k')满足这些条件,附加条件k > = k' < = 我 ',如果 = =我' j < = j'还会见了。换句话说,所有的最大匹配块,返回一个启动最早的,和所有那些大匹配块的最早在开始,返回在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, 血压, 0)

2.6 版本中的更改:此方法返回名为元组匹配 (a、 b、 大小)

get_matching_blocks()

返回描述匹配子序列的三元组列表。每个三是窗体的(我, j, n),意味着那[i:i + n] = = b [j:j + n]三元组在第一和j递增。

最后三人是假的和具有价值(len(a), len(b), 0)它是唯一的三人房含n = = 0如果(我, j, n)(我 ', j', n')是相邻三元组列表中,并且第二个不是中列表中的最后一个三元组然后第一 + n ! = 我 'j + n ! = j'换句话说,相邻的三元组总是描述非相邻相等的块。

2.5 版本中的更改:实现了相邻的三元组总是描述非相邻块的保证。

>>> 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 元组返回列表描述如何将变成b每个元组是窗体的(标记, i1、 i2、 j1、 j2)第一个元组有i1 = = j1 = = 0,还有i1等于从前面的元组,和,同样,等于以前的j2 j1 i2剩余的元组。

标记值是字符串,带有这些含义:

Value Meaning
'replace' a[i1:i2] should be replaced by b[j1:j2].
'delete' a[i1:i2] should be deleted. Note that j1 == j2 in this case.
'insert' b[j1:j2] should be inserted at a[i1:i1]. Note that i1 == i2 in this case.
'equal' a[i1:i2] == b[j1:j2] (the sub-sequences are equal).

举个例子:

>>> a = "qabxcd"
>>> b = "abycdf"
>>> s = SequenceMatcher(None, a, b)
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
...    print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" %
...           (tag, i1, i2, a[i1:i2], j1, j2, b[j1:j2]))
 delete a[0:1] (q) b[0:0] ()
  equal a[1:3] (ab) b[0:2] (ab)
replace a[3:4] (x) b[2:3] (y)
  equal a[4:6] (cd) b[3:5] (cd)
 insert a[6:6] () b[5:6] (f)
get_grouped_opcodes([n])

返回一种发电机的上下文的n行与团体。

开始与团体经由get_opcodes(),这种方法拆分出变化的较小群集和消除没有更改的干预范围。

组在get_opcodes()相同的格式返回。

新版本 2.3。

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

7.4.2. SequenceMatcher Examples

本示例比较两个字符串,考虑空白要"垃圾:"

>>> s = SequenceMatcher(lambda x: x == " ",
...                     "private Thread currentThread;",
...                     "private volatile Thread currentThread;")

ratio()返回一个浮点数在 [0,1],测量序列的相似性。作为一个经验法则,在 0.6 手段的序列是一个ratio()值接近的匹配项:

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

请参见

7.4.3. Differ Objects

注意不同的-生成的增量使不自称是极小的差异。与此相反,极小的差异往往违反直觉的因为他们可能,有时意外的比赛 100 页分开角落同步。连续匹配限制同步点保留一些概念的地方,偶尔的成本产生更长的时间差异。

不同类具有此构造函数:

class difflib.Differ([linejunk[, charjunk]])

可选的关键字参数linejunkcharjunk是过滤函数 (或没有):

linejunk: 一个函数,接受单个字符串参数,并返回 true,如果字符串是垃圾。默认值是没有意义没有线被认为是垃圾。

charjunk: 一个函数,接受单个字符参数 (长度为 1 的字符串),并返回 true,则该字符是垃圾。默认值是意义的照片中没有字符被认为是垃圾。

不同对象通过一个单独的方法是使用 (增量生成):

compare(a, b)

比较两个序列的行,并生成三角洲 (行序列)。

每个序列必须包含以换行符结尾的个别单行字符串。这些序列可索取文件类似物体的readlines()方法。生成的三角洲还包括换行符终止的字符串,准备作为打印-是通过文件类似对象的writelines()方法。

7.4.4. Differ Example

本示例将两个文本进行比较。首先,我们建立的案文,以换行符结尾的各个单行字符串序列 (这种序列可以也得到从文件类似物体的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(1)
>>> 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(1)

下一步我们实例化一个不同的对象:

>>> d = Differ()

请注意当不同对象进行实例化时我们可以通过函数来过滤掉线和字符"垃圾"。请参阅Differ()构造函数的详细信息。

最后,我们比较这两个:

>>> result = list(d.compare(text1, text2))

结果是一个字符串列表,所以让我们漂亮的格式打印:

>>> 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.

7.4.5. A command-line interface to difflib

此示例演示如何使用 difflib 来创建一个diff-喜欢实用程序。它也包含在 Python 源代码发行版,作为Tools/scripts/diff.py

""" 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, time, difflib, optparse

def main():
     # Configure the option parser
    usage = "usage: %prog [options] fromfile tofile"
    parser = optparse.OptionParser(usage)
    parser.add_option("-c", action="store_true", default=False,
                      help='Produce a context format diff (default)')
    parser.add_option("-u", action="store_true", default=False,
                      help='Produce a unified format diff')
    hlp = 'Produce HTML side by side diff (can use -c and -l in conjunction)'
    parser.add_option("-m", action="store_true", default=False, help=hlp)
    parser.add_option("-n", action="store_true", default=False,
                      help='Produce a ndiff format diff')
    parser.add_option("-l", "--lines", type="int", default=3,
                      help='Set number of context lines (default 3)')
    (options, args) = parser.parse_args()

    if len(args) == 0:
        parser.print_help()
        sys.exit(1)
    if len(args) != 2:
        parser.error("need to specify both a fromfile and tofile")

    n = options.lines
    fromfile, tofile = args # as specified in the usage string

    # we're passing these as arguments to the diff function
    fromdate = time.ctime(os.stat(fromfile).st_mtime)
    todate = time.ctime(os.stat(tofile).st_mtime)
    fromlines = open(fromfile, 'U').readlines()
    tolines = open(tofile, 'U').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)

    # we're using writelines because diff is a generator
    sys.stdout.writelines(diff)

if __name__ == '__main__':
    main()