【问题标题】:Generating and applying diffs in python在 python 中生成和应用差异
【发布时间】:2025-12-20 03:25:11
【问题描述】:

在 python 中是否有一种“开箱即用”的方式来生成两个文本之间的差异列表,然后将此差异应用于一个文件以获得另一个文件?

我想保留文本的修订历史记录,但如果只有一个编辑行,我不想保存每个修订的整个文本。我查看了difflib,但看不到如何生成仅包含已编辑行的列表,这些行仍可用于修改一个文本以获取另一个文本。

【问题讨论】:

    标签: python diff revision difflib


    【解决方案1】:

    你看过谷歌的 diff-match-patch 吗?显然 google Docs 使用了这组算法。它不仅包含差异模块,还包含补丁模块,因此您可以从旧文件和差异中生成最新文件。

    包含一个 python 版本。

    http://code.google.com/p/google-diff-match-patch/

    【讨论】:

    • 正是我想要的!我尝试在谷歌上搜索“python”、“diff”、“patch”、“revision”的不同组合,但还没有找到。
    • google-diff-match-patch 似乎确实存储了整个文件。它将所有内容保存在元组中: (0, 'stuff') 表示两个字符串中都存在 'stuff'。该系统非常简单,它几乎可以存储每个字符,以便它可以遍历它们并根据需要修改文本。
    • 我如何在 Python 中使用这个 API>?如果能举例说明就好了
    • 它仍然以元组的形式存储整个文本,并与删除和插入的文本混合在一起。当您必须将其放入 ram 中时,它过于冗长和庞大......
    【解决方案2】:

    difflib.unified_diff 确实想要你想要的吗?有一个例子here

    【讨论】:

    • 投票支持您的答案。内置的 difflib 看起来很强大,但有点令人困惑,只是克服学习曲线的问题。在这里查看我的类似帖子:*.com/questions/4743359/…
    • 库无法应用difflib.unified_diff 的输出。它有diff,但没有patch。因此,如果你想留在 python 中,`difflib.unified_diff` 是没用的。
    【解决方案3】:

    我已经实现了一个纯 python 函数来应用差异补丁来恢复任一输入字符串,我希望有人觉得它有用。它使用解析Unified diff format

    import re
    
    _hdr_pat = re.compile("^@@ -(\d+),?(\d+)? \+(\d+),?(\d+)? @@$")
    
    def apply_patch(s,patch,revert=False):
      """
      Apply unified diff patch to string s to recover newer string.
      If revert is True, treat s as the newer string, recover older string.
      """
      s = s.splitlines(True)
      p = patch.splitlines(True)
      t = ''
      i = sl = 0
      (midx,sign) = (1,'+') if not revert else (3,'-')
      while i < len(p) and p[i].startswith(("---","+++")): i += 1 # skip header lines
      while i < len(p):
        m = _hdr_pat.match(p[i])
        if not m: raise Exception("Cannot process diff")
        i += 1
        l = int(m.group(midx))-1 + (m.group(midx+1) == '0')
        t += ''.join(s[sl:l])
        sl = l
        while i < len(p) and p[i][0] != '@':
          if i+1 < len(p) and p[i+1][0] == '\\': line = p[i][:-1]; i += 2
          else: line = p[i]; i += 1
          if len(line) > 0:
            if line[0] == sign or line[0] == ' ': t += line[1:]
            sl += (line[0] != sign)
      t += ''.join(s[sl:])
      return t
    

    如果有标题行 ("--- ...\n","+++ ...\n") 它会跳过它们。如果我们有一个统一的差异字符串diffstr 代表oldstrnewstr 之间的差异:

    # recreate `newstr` from `oldstr`+patch
    newstr = apply_patch(oldstr, diffstr)
    # recreate `oldstr` from `newstr`+patch
    oldstr = apply_patch(newstr, diffstr, True)
    

    在 Python 中,您可以使用 difflib(标准库的一部分)生成两个字符串的统一差异:

    import difflib
    _no_eol = "\ No newline at end of file"
    
    def make_patch(a,b):
      """
      Get unified string diff between two strings. Trims top two lines.
      Returns empty string if strings are identical.
      """
      diffs = difflib.unified_diff(a.splitlines(True),b.splitlines(True),n=0)
      try: _,_ = next(diffs),next(diffs)
      except StopIteration: pass
      return ''.join([d if d[-1] == '\n' else d+'\n'+_no_eol+'\n' for d in diffs])
    

    在 unix 上:diff -U0 a.txt b.txt

    代码在 GitHub 上,以及使用 ASCII 和随机 unicode 字符的测试:https://gist.github.com/noporpoise/16e731849eb1231e86d78f9dfeca3abc

    【讨论】:

      【解决方案4】:

      AFAIK 大多数差异算法使用简单的Longest Common Subsequence 匹配,以找到两个文本之间的共同部分,剩下的部分被认为是差异。在 python 中编写自己的动态编程算法来实现这一点应该不难,上面的*页面也提供了算法。

      【讨论】:

        【解决方案5】:

        必须是 python 解决方案吗?
        对于解决方案,我的第一个想法是使用版本控制系统(Subversion、Git 等)或 diff / patch 实用程序,它们是 unix 系统的标准,或者是 cygwin 的一部分基于 Windows 的系统。

        【讨论】:

        • 它必须是一个纯 python 解决方案,因为我想将它部署在 AppEngine 中。 diff/patch 是理想的,但在 python 中。
        • 请注意,这种计算通常很慢,所以可能坚持使用较低级别的东西会更好地扩展!
        【解决方案6】:

        也许您可以使用unified_diff 来生成文件中的差异列表。只有文件中更改的文本才能写入新的文本文件,供您将来参考。 这是帮助您仅将差异写入新文件的代码。 我希望这就是您的要求!

        diff = difflib.unified_diff(old_file, new_file, lineterm='')
            lines = list(diff)[2:]
            # linesT = list(diff)[0:3]
            print (lines[0])
            added = [lineA for lineA in lines if lineA[0] == '+']
        
        
            with open("output.txt", "w") as fh1:
             for line in added:
               fh1.write(line)
            print '+',added
            removed = [lineB for lineB in lines if lineB[0] == '-']
            with open("output.txt", "a") as fh1:
             for line in removed:
               fh1.write(line)
            print '-',removed 
        

        在您的代码中使用它来仅保存差异输出!

        【讨论】: