difflib-序列比较类(12) – Python语言(必读进阶学习教程)(参考资料)
该模块提供用于比较序列的类和函数。例如,它可以用于比较文件,并且可以生成各种格式的差异信息,包括 HTML 和上下文以及统一差异。要比较目录和文件,另请参阅filecmp模块。
- class
difflib.SequenceMatcher - 这是一个灵活的类,用于比较任何类型的序列对,只要序列元素是可散列的。基本算法早于 Ratcliff 和 Obershelp 在 1980 年代后期以双曲线名称“格式塔模式匹配”发布的算法,并且比它更高级一些。这个想法是找到不包含“垃圾”元素的最长连续匹配子序列;这些“垃圾”元素在某种意义上是无趣的,例如空行或空格。(处理垃圾是 Ratcliff 和 Obershelp 算法的扩展。)然后将相同的想法递归地应用于匹配子序列左侧和右侧的序列片段。这不会产生最少的编辑序列,但会产生对人们“看起来正确”的匹配。
时序:基本的 Ratcliff-Obershelp 算法在最坏情况下是三次时间,在预期情况下是二次时间。
SequenceMatcher是最坏情况的二次时间,并且预期情况的行为以复杂的方式取决于序列有多少共同元素;最佳情况时间是线性的。自动垃圾启发式:
SequenceMatcher支持自动将某些序列项视为垃圾的启发式。启发式计算每个单独项目在序列中出现的次数。如果一个项目的重复项(在第一个之后)占序列的 1% 以上,并且序列长度至少为 200 个项目,则该项目被标记为“流行”并被视为垃圾,以进行序列匹配。可以通过autojunk在False创建SequenceMatcher.3.2 新版功能: autojunk参数。
- class
difflib.Differ - 这是一个用于比较文本行序列并产生人类可读差异或增量的类。Differ
SequenceMatcher既用于比较行序列,也用于比较相似(接近匹配)行中的字符序列。delta的每一行都
Differ以两个字母的代码开头:
-
码 含义 '- '序列1独有的行 '+ '序列2独有的行 ' '两个序列共有的线 '? '两个输入序列中都不存在该行 以’
?‘ 开头的行试图引导眼睛注意到内部差异,并且在任一输入序列中都不存在。如果序列包含制表符,这些行可能会造成混淆。
- class
difflib.HtmlDiff - 此类可用于创建HTML表(或包含表的完整HTML文件),并逐行显示文本与行间和行内更改突出显示的逐行比较。该表可以在完全或上下文差异模式下生成。
这个类的构造函数是:
tabsize=8__init__(wrapcolumn=None,linejunk=None,charjunk=IS_CHARACTER_JUNK,)- 初始化实例
HtmlDiff。tabsize是一个可选的关键字参数,用于指定制表位间距和默认值
8。wrapcolumn是一个可选的关键字,用于指定拆分和包装行的列号,默认为
None未包装行的位置。linejunk和charjunk是传入的可选关键字参数
ndiff()(用于HtmlDiff生成并排的HTML差异)。有关ndiff()参数默认值和说明,请参阅 文档。
以下方法是公开的:
fromlinesmake_file(tolines,fromdesc=”,todesc=”,context=False,numlines=5,*,charset=’utf-8′,)- 比较fromlines和tolines(字符串列表)并返回一个字符串,该字符串是一个完整的HTML文件,其中包含一个表格,显示逐行差异,突出显示行间和行内更改。
fromdesc和todesc是可选的关键字参数,用于指定从/到文件列标题字符串(默认为空字符串)。
context和numlines都是可选的关键字参数。设置方面,以
True当背景的差异将被显示,否则默认为False可显示完整的文件。numlines默认为5。当上下文 为Truenumlines时,控制围绕差异突出显示的上下文行数。当上下文为Falsenumlines时,控制使用“下一个”超链接时差异高亮显示之前显示的行数(设置为零会导致“下一个”超链接将下一个差异高亮显示在浏览器顶部而没有任何前导上下文)。版本3.5中已更改:添加了charset仅关键字参数。HTML文档的默认字符集从更改
'ISO-8859-1'为'utf-8'。
fromlinesmake_table(tolines,fromdesc=”,todesc=”,context=False,numlines=5,)- 比较fromlines和tolines(字符串列表)并返回一个字符串,该字符串是一个完整的HTML表格,显示逐行差异,突出显示行间和行内更改。
此方法的参数与方法的参数相同
make_file()。
Tools/scripts/diff.py是这个类的命令行前端,并包含一个很好的使用示例。
difflib.acontext_diff(b,fromfile=”,tofile=”,fromfiledate=”,tofiledate=”,n=3,lineterm=’\n’,)- 比较a和b(字符串列表); 以上下文diff格式返回delta(生成delta行的生成器)。
上下文差异是一种紧凑的方式,只显示已更改的行加上几行上下文。更改以前/后样式显示。上下文行的数量由n设置,默认为3。
默认情况下,diff控制线(带
***或的那些---)是使用尾随换行符创建的。这是有帮助的,因此由于输入和输出都具有尾随换行符io.IOBase.readlines(),因此io.IOBase.writelines()从输入和输出中产生的输入创建的输入 适合使用 。对于没有尾随换行符的输入,请将lineterm参数设置为以
""使输出统一为newline free。上下文差异格式通常具有文件名和修改时间的标头。可以使用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(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.wordget_close_matches(possibilities,n=3,cutoff=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.andiff(b,linejunk=None,charjunk=IS_CHARACTER_JUNK,)- 比较a和b(字符串列表); 返回a-
Differstyle delta(生成三角形线的生成器)。可选的关键字参数linejunk和charjunk是过滤功能(或
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.sequencerestore(which,)- 返回生成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.aunified_diff(b,fromfile=”,tofile=”,fromfiledate=”,tofiledate=”,n=3,lineterm=’\n’,)- 比较a和b(字符串列表); 以统一的diff格式返回delta(生成delta行的生成器)。
统一差异是一种紧凑的方式,只显示已更改的行加上几行上下文。更改以内联样式显示(而不是在块之前/之后单独)。上下文行的数量由n设置,默认为3。
默认情况下,差异控制线(那些
---,+++或@@)与尾部换行符创建。这是有帮助的,因此由于输入和输出都具有尾随换行符io.IOBase.readlines(),因此io.IOBase.writelines()从输入和输出中产生的输入创建的输入适合使用 。对于没有尾随换行符的输入,请将lineterm参数设置为以
""使输出统一为newline free。上下文差异格式通常具有文件名和修改时间的标头。可以使用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.dfuncdiff_bytes(a,b,fromfile=b”,tofile=b”,fromfiledate=b”,tofiledate=b”,n=3,lineterm=b’\n’,)- 使用dfunc比较a和b(字节对象列表); 以dfunc返回的格式生成一系列delta行(也是字节)。 dfunc必须是可调用的,通常是或者 。
unified_diff()context_diff()允许您比较具有未知或不一致编码的数据。除n之外的所有输入必须是字节对象,而不是str。通过无损地将所有输入(除了n)转换为str和调用来工作。然后将dfunc的输出 转换回字节,因此您收到的delta行与a和b具有相同的未知/不一致编码。
dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)版本3.5中的新功能。
difflib.lineIS_LINE_JUNK()- 对于可忽略的行返回true。线线是可忽略的,如果线为空或包含一个单一的
'#',否则就不是忽略。作为参数的默认linejunk在ndiff()旧版本中。
difflib.chIS_CHARACTER_JUNK()- 对可忽略的字符返回 True。 如果 ch 是空格或制表符,则字符 ch 可忽略,否则不可忽略。 在 ndiff() 中用作参数 charjunk 的默认值。
SequenceMatcher对象
该SequenceMatcher班有这样的构造函数:
- class
difflib.isjunk=NoneSequenceMatcher(a=”,b=”,autojunk=True,) - 可选参数isjunk必须是
None(缺省值)或带有sequence元素的单参数函数,当且仅当元素是“垃圾”并且应该被忽略时才返回true。通过None对isjunk相当于通过; 换句话说,没有元素被忽略。例如,通过:lambda x: 0lambda x: x in " \t"
如果你将线条比较为字符序列,并且不想在空白或硬标签上进行同步。
可选参数a和b是要比较的序列; 两者都默认为空字符串。两个序列的元素必须是可清除的。
可选参数autojunk可用于禁用自动垃圾启发式。
新版本3.2:在autojunk参数。
SequenceMatcher对象获得三个数据属性:bjunk是b的元素集,isjunk是
True; bpopular是启发式考虑的非垃圾元素集(如果它没有被禁用); b2j是一个字典,它将b的剩余元素映射到它们出现的位置列表。每当用或复位b时,所有三个都复位。set_seqs()set_seq2()新版本3.2:在bjunk和bpopular属性。
SequenceMatcher对象具有以下方法:set_seqs(a,b )- 设置要比较的两个序列。
SequenceMatcher计算和缓存有关第二个序列的详细信息,因此如果要将一个序列与多个序列进行比较,请使用set_seq2()一次设置常用序列并set_seq1()重复调用,每个其他序列一次。set_seq1(a )- 设置要比较的第一个序列。要比较的第二个序列没有改变。
set_seq2(b )- 设置要比较的第二个序列。要比较的第一个序列没有改变。
alofind_longest_match(ahi,blo,bhi,)- 在
a[alo:ahi]和中找到最长的匹配块b[blo:bhi]。如果isjunk省略或
None,find_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()- 返回描述非重叠匹配子序列的三元组列表。每个三元组都是形式,意思是。三元组在i和j中单调递增。
(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'
n=3get_grouped_opcodes()- 返回具有多达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.linejunk=NoneDiffer(charjunk=None,) - 可选关键字参数linejunk和charjunk用于过滤函数(或
None):linejunk:接受单个字符串参数的函数,如果字符串是垃圾,则返回true。默认值是
None,意味着没有行被视为垃圾。charjunk:接受单个字符参数(长度为1的字符串)的函数,如果字符是垃圾,则返回true。默认值是
None,意味着没有字符被视为垃圾。这些垃圾过滤功能可加快匹配以查找差异,并且不会导致忽略任何不同的行或字符。阅读
find_longest_match()方法的isjunk 参数的说明以获得解释。Differ通过单个方法使用对象(生成的增量):acompare(b,)- 比较两个行序列,并生成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()