Python difflib:如何内联突出显示差异?

34

比较相似的行时,我希望能够突出显示同一行上的差异:

a) lorem ipsum dolor sit amet
b) lorem foo ipsum dolor amet

lorem <ins>foo</ins> ipsum dolor <del>sit</del> amet

虽然 difflib.HtmlDiff 可以实现这种行内高亮,但它产生的标记非常冗长。

不幸的是,我没有找到另一种不基于逐行处理的类/方法。

我有什么遗漏吗? 任何提示都会受到赞赏!

3个回答

54

对于您的简单示例:

import difflib
def show_diff(seqm):
    """Unify operations between two compared strings
seqm is a difflib.SequenceMatcher instance whose a & b are strings"""
    output= []
    for opcode, a0, a1, b0, b1 in seqm.get_opcodes():
        if opcode == 'equal':
            output.append(seqm.a[a0:a1])
        elif opcode == 'insert':
            output.append("<ins>" + seqm.b[b0:b1] + "</ins>")
        elif opcode == 'delete':
            output.append("<del>" + seqm.a[a0:a1] + "</del>")
        elif opcode == 'replace':
            raise NotImplementedError("what to do with 'replace' opcode?")
        else:
            raise RuntimeError("unexpected opcode")
    return ''.join(output)

>>> sm= difflib.SequenceMatcher(None, "lorem ipsum dolor sit amet", "lorem foo ipsum dolor amet")
>>> show_diff(sm)
'lorem<ins> foo</ins> ipsum dolor <del>sit </del>amet'

这适用于字符串。您应该决定如何处理“replace”操作码。


非常感谢这个! 这正是我所需要的样例。我不知道该如何开始,但这很好地说明了它。 再次感谢! - AnC
+1 感谢你的示例 :) 你建议如何处理替换操作码? - Viet
一个建议是在实践中发现一些“替换”操作码;文档说它们可以被生成,但我不记得曾经看到过任何(如果我没记错,我只看到过“删除”后面跟着“插入”)。无论如何,如何处理“替换”取决于OP。 - tzot
1
https://docs.python.org/2/library/difflib.html#difflib.SequenceMatcher.get_opcodes 上的示例包含替换操作码。 - Tom
2
对于替换操作码,我只是将插入操作和删除操作的值都附加在了一起。 - ThorSummoner

9

这是一个受 @tzot 的 上面的回答 启发的内联差异工具(同时也兼容Python 3):

def inline_diff(a, b):
    import difflib
    matcher = difflib.SequenceMatcher(None, a, b)
    def process_tag(tag, i1, i2, j1, j2):
        if tag == 'replace':
            return '{' + matcher.a[i1:i2] + ' -> ' + matcher.b[j1:j2] + '}'
        if tag == 'delete':
            return '{- ' + matcher.a[i1:i2] + '}'
        if tag == 'equal':
            return matcher.a[i1:i2]
        if tag == 'insert':
            return '{+ ' + matcher.b[j1:j2] + '}'
        assert False, "Unknown tag %r"%tag
    return ''.join(process_tag(*t) for t in matcher.get_opcodes())

虽然不完美,例如,扩展“replace”操作码以识别替换的整个单词而不仅仅是几个不同的字母会更好,但这是一个很好的开始。

示例输出:

>>> a='Lorem ipsum dolor sit amet consectetur adipiscing'
>>> b='Lorem bananas ipsum cabbage sit amet adipiscing'
>>> print(inline_diff(a, b))
Lorem{+  bananas} ipsum {dolor -> cabbage} sit amet{-  consectetur} adipiscing

我喜欢你如何处理“替换”选项。 - Josiah Yoder
1
我也很喜欢你将它翻译成了Python 3。现在我也已经将tzot的原始答案翻译成了Python 3。 - Josiah Yoder
但是,用Python推导式中调用的方法替换嵌套if循环真的更清晰吗? - Josiah Yoder
1
@JosiahYoder 对于for循环和推导式的问题,这是一个公正的问题。由于这是一个风格问题,我不确定除了个人偏好之外是否有明确的答案。 - orip

3

difflib.SequenceMatcher 可以用于单行操作。您可以使用“opcodes”来确定如何更改第一行以使其成为第二行。


1
我恐怕还不太理解这个 - 不过我会继续深入挖掘。谢谢。 - AnC
你到底想用这些差异做什么?你想要HTML输出还是只是使用HtmlDiff进行内联差异比较? - Adam
尽管 HTML 输出是我的主要使用情况,但 HtmlDiff 的输出不容易重复使用 - 也就是说,如果它只是插入 INS 和 DEL,那么它可以很容易地转换为下一步所需的任何内容。 - AnC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接