1. difflib差异计算工具
此模块提供用于比较序列的类和函数。 例如,它可以用于比较文件,并可以产生各种格式的不同信息,包括HTML和上下文以及统一格式的差异点。有关目录和文件的比较,请参见filecmp模块。
class difflib.SequenceMatcher(None,s1,s2)
这是一个灵活的类,可用于比较任何类型的序列对,只要序列元素为 hashable 对象。 其基本算法要早于由 Ratcliff 和 Obershelp 于 1980 年代末期发表并以“格式塔模式匹配”的夸张名称命名的算法,并且更加有趣一些。 其思路是找到不包含“垃圾”元素的最长连续匹配子序列;所谓“垃圾”元素是指其在某种意义上没有价值,例如空白行或空白符。 (处理垃圾元素是对 Ratcliff 和 Obershelp 算法的一个扩展。) 然后同样的思路将递归地应用于匹配序列的左右序列片段。 这并不能产生最小编辑序列,但确实能产生在人们看来“正确”的匹配。
1.1 比较文本体
differ类用于处理文本行序列,并生成人类可读的差异(deltas)或更改指令各行中的差异。differ生成的默认输出与unix下的diff命令行工具类似,包括表的原始输入值(包含共同的值),以及指示做了哪些更改的标记数据。
有 - 前缀的行在
第一个序列中,而非第二个序列。
有 + 前缀的行在第二个序列中,而非第一个序列。
如果某一行的版本之间存在增量差异,那么会使用一个加 ? 前缀以突出在新版本中的更改。
如果一行未改变,则会打印输出,而且其左列有一个额外的空格,使它与其有差异的输出对齐。
将文本传入compare()之前先将其分解为由单个文本行构成的序列,与传入串相比,这样可以生成更可读的输出。
import difflib text1 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convalis. Nam sed sem vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus.""" text1_lines = text1.splitlines() text2 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convalis. Nam cras vitae mi vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus. Suspendisse eu lectus. In nunc.""" text2_lines = text2.splitlines() d = difflib.Differ() diff = d.compare(text1_lines, text2_lines) print(' '.join(diff))
结果:
示例数据中两个文本段的开始部分是一样的,所以第一行会直接打印而没有任何额外标注。
数据的第三行有变化,修改后的文本中包含有一个逗号。这两个版本的数据行都会打印,而且第五行上的额外信息会显示文本中哪一列有修改,这里显示增加了 , 字符。
输出中接下来几行显示删除了一个多余的空格。
接下来有一个更复杂的改变,其替换了一个短语中的多个单词。
段落中最后一句变化最大,所以表示差异时完全删除了老版本,增加了新版本。
ndiff()函数生成的输出基本上相同,通过特别“加工”来处理文本数据,并删除输入中的“噪声”。
differ()类会显示所有输入行,统一差异格式(unified diff)则不同,它只包含有修改的文本行和一些上下文。unified_diff()函数会生成这种输出。
import difflib text1 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convalis. Nam sed sem vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus.""" text1_lines = text1.splitlines() text2 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convalis. Nam cras vitae mi vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus. Suspendisse eu lectus. In nunc.""" text2_lines = text2.splitlines() diff = difflib.unified_diff( text1_lines, text2_lines, lineterm='', ) print(' '.join(diff))
lineterm参数用来告诉unified_diff()不必为它返回的控制行追加换行符,因为输入行不包括这些换行符。打印时所有行都会增加换行符。对于很多常用版本控制工具的用户来说,输出看上去应该很熟悉。
使用context_diff()会产生类似的可续输出。
1.2 无用数据
所有生成差异序列的函数都会接受一些参数来指示应当忽略哪些行,以及要忽略一行中的哪些字符。例如,这些参数可用于跳过文件两个版本中的标记或空白符改变。
from difflib import SequenceMatcher def show_results(match): print(' a = {}'.format(match.a)) print(' b = {}'.format(match.b)) print(' size = {}'.format(match.size)) i, j, k = match print(' A[a:a+size] = {!r}'.format(A[i:i + k])) print(' B[b:b+size] = {!r}'.format(B[j:j + k])) A = " abcd" B = "abcd abcd" print('A = {!r}'.format(A)) print('B = {!r}'.format(B)) print(' Without junk detection:') s1 = SequenceMatcher(None, A, B) match1 = s1.find_longest_match(0, len(A), 0, len(B)) show_results(match1) print(' Treat spaces as junk:') s2 = SequenceMatcher(lambda x: x == " ", A, B) match2 = s2.find_longest_match(0, len(A), 0, len(B)) show_results(match2)
默认differ不会显式地忽略任何行或字符,但会依赖SequenceMatcher的能力检测噪声。ndiff()的默认行为是忽略空格和制表符。
1.3 比较任意类型
SequenceMatcher类可以比较任意类型的两个序列,只要它们的值是可散列的。这个类使用一个算法来标识序列中最长的连续匹配块,并删除对实际数据没有贡献的无用值。
函数get_opcodes()返回一个指令列表来修改第一个序列,使它与第二个序列匹配。这些指令被编码为5元素元组,包括一个字符串指令(“操作码”)和序列的两对开始及结束索引(表示为i1、i2、j1和j2)。
值 |
意义 |
---|---|
|
|
|
|
|
|
|
|
import difflib s1 = [1, 2, 3, 5, 6, 4] s2 = [2, 3, 5, 4, 6, 1] print('Initial data:') print('s1 =', s1) print('s2 =', s2) print('s1 == s2:', s1 == s2) print() matcher = difflib.SequenceMatcher(None, s1, s2) for tag, i1, i2, j1, j2 in reversed(matcher.get_opcodes()): if tag == 'delete': print('Remove {} from positions [{}:{}]'.format( s1[i1:i2], i1, i2)) print(' before =', s1) del s1[i1:i2] elif tag == 'equal': print('s1[{}:{}] and s2[{}:{}] are the same'.format( i1, i2, j1, j2)) elif tag == 'insert': print('Insert {} from s2[{}:{}] into s1 at {}'.format( s2[j1:j2], j1, j2, i1)) print(' before =', s1) s1[i1:i2] = s2[j1:j2] elif tag == 'replace': print(('Replace {} from s1[{}:{}] ' 'with {} from s2[{}:{}]').format( s1[i1:i2], i1, i2, s2[j1:j2], j1, j2)) print(' before =', s1) s1[i1:i2] = s2[j1:j2] print(' after =', s1, ' ') print('s1 == s2:', s1 == s2)
这个例子比较了两个整数列表,并使用get_opcodes()得出将原列表转换为新列表的指令。这里以逆序应用所做的修改,以便增加和删除元素之后列表索引仍是正确的。
SequenceMatcher用于处理定制类以及内置类型,前提是它们必须是可散列的。