【问题标题】:Python Difflib Deltas and Compare NdiffPython Difflib Deltas 和比较 Ndiff
【发布时间】:2011-01-20 04:10:53
【问题描述】:

我想做一些类似于我相信变更控制系统所做的事情,它们比较两个文件,并在每次文件更改时保存一个小的差异。 我一直在阅读此页面:http://docs.python.org/library/difflib.html,显然它并没有陷入我的脑海。

我试图在下面显示的一个简单的程序中重新创建它, 但我似乎缺少的是 Delta 包含的内容至少与原始文件一样多,甚至更多。

难道不能只做纯粹的改变吗? 我问的原因很明显 - 节省磁盘空间。
我可以每次只保存整个代码块,但最好保存一次当前代码,然后保存更改的小差异。

我还在试图弄清楚为什么许多 difflib 函数返回一个生成器而不是一个列表,那里有什么优势?

difflib 对我有用吗?还是我需要找到一个更专业、功能更多的软件包?

# Python Difflib demo 
# Author: Neal Walters 
# loosely based on http://ahlawat.net/wordpress/?p=371
# 01/17/2011 

# build the files here - later we will just read the files probably 
file1Contents="""
for j = 1 to 10: 
   print "ABC"
   print "DEF" 
   print "HIJ"
   print "JKL"
   print "Hello World"
   print "j=" + j 
   print "XYZ"
"""

file2Contents = """
for j = 1 to 10: 
   print "ABC"
   print "DEF" 
   print "HIJ"
   print "JKL"
   print "Hello World"
   print "XYZ"
print "The end"
"""

filename1 = "diff_file1.txt" 
filename2 = "diff_file2.txt" 

file1 = open(filename1,"w") 
file2 = open(filename2,"w") 

file1.write(file1Contents) 
file2.write(file2Contents) 

file1.close()
file2.close() 
#end of file build 

lines1 = open(filename1, "r").readlines()
lines2 = open(filename2, "r").readlines()

import difflib

print "\n FILE 1 \n" 
for line in lines1:
  print line 

print "\n FILE 2 \n" 
for line in lines2: 
  print line 

diffSequence = difflib.ndiff(lines1, lines2) 

print "\n ----- SHOW DIFF ----- \n" 
for i, line in enumerate(diffSequence):
    print line

diffObj = difflib.Differ() 
deltaSequence = diffObj.compare(lines1, lines2) 
deltaList = list(deltaSequence) 

print "\n ----- SHOW DELTALIST ----- \n" 
for i, line in enumerate(deltaList):
    print line



#let's suppose we store just the diffSequence in the database 
#then we want to take the current file (file2) and recreate the original (file1) from it
#by backward applying the diff 

restoredFile1Lines = difflib.restore(diffSequence,1)  # 1 indicates file1 of 2 used to create the diff 

restoreFileList = list(restoredFile1Lines)

print "\n ----- SHOW REBUILD OF FILE1 ----- \n" 
# this is not showing anything! 
for i, line in enumerate(restoreFileList): 
    print line

谢谢!

更新:

contextDiffSeq = difflib.context_diff(lines1, lines2) 
contextDiffList = list(contextDiffSeq) 

print "\n ----- SHOW CONTEXTDIFF ----- \n" 
for i, line in enumerate(contextDiffList):
    print line

----- 显示上下文 -----




* 5,9 **

 print "HIJ"

 print "JKL"

 print "Hello World"
  • 打印 "j=" + j

    打印“XYZ”

--- 5,9 ----

 print "HIJ"

 print "JKL"

 print "Hello World"

 print "XYZ"
  • 打印“结束”

另一个更新:

在 Panvalet an Librarian 的旧时代,大型机的源代码管理工具,您可以创建这样的变更集:

++ADD 9
   print "j=" + j 

这只是意味着在第 9 行之后添加一行(或多行)。 然后是 ++REPLACE 或 ++UPDATE 之类的单词。 http://www4.hawaii.gov/dags/icsd/ppmo/Stds_Web_Pages/pdf/it110401.pdf

【问题讨论】:

    标签: python delta difflib


    【解决方案1】:

    我还在想办法 为什么许多 difflib 函数返回一个 生成器而不是列表,什么是 有什么优势?

    好吧,考虑一下 - 如果您比较文件,这些文件在理论上(并且在实践中)可能会很大 - 例如,将 delta 作为列表返回意味着将完整数据读入内存,这不是明智之举。

    至于只返回差值,嗯,使用生成器还有另一个优势 - 只需遍历 delta 并保留您感兴趣的任何行。

    如果您阅读 difflib documentation 的不同样式增量,您会看到这样一段文字:

    Each line of a Differ delta begins with a two-letter code:
    Code    Meaning
    '- '    line unique to sequence 1
    '+ '    line unique to sequence 2
    '  '    line common to both sequences
    '? '    line not present in either input sequence
    

    因此,如果您只想要差异,您可以使用 str.startswith 轻松过滤掉它们

    您还可以使用difflib.context_diff 获得一个仅显示更改的紧凑增量。

    【讨论】:

      【解决方案2】:

      差异必须包含足够的信息,以便可以将一个版本修补到另一个版本中,所以是的,对于您对非常小的文档进行单行更改的实验,存储整个文档可能会更便宜。

      库函数返回迭代器,以便在内存紧张或只需要查看部分结果序列的客户端上更轻松。在 Python 中是可以的,因为每个迭代器都可以转换为具有非常短的 list(an_iterator) 表达式的列表。

      大多数差异是在文本行上完成的,但也可以逐个字符地进行,difflib 可以做到这一点。查看difflib 中的Differ 对象类。

      各地的示例都使用人性化的输出,但差异在内部以更紧凑、计算机友好的方式进行管理。此外,差异通常包含冗余信息(如要删除的行的文本),以使修补和合并更改安全。如果您愿意,可以通过您自己的代码删除冗余。

      我刚刚读到difflib 选择最小惊喜来支持最优性,这是我不会反对的。有well known 算法可以快速产生最小的更改集。

      我曾经在大约 1250 行 Java (JRCS) 中编写了一个通用差异引擎以及一个最佳算法。它适用于可以比较是否相等的任何元素序列。如果您想构建自己的解决方案,我认为 JRCS 的翻译/重新实现应该不超过 300 行 Python。

      处理difflib 产生的输出以使其更紧凑也是一种选择。这是一个小文件的示例,其中包含三个更改(添加、更改和删除):

      ---  
      +++  
      @@ -7,0 +7,1 @@
      +aaaaa
      @@ -9,1 +10,1 @@
      -c= 0
      +c= 1
      @@ -15,1 +16,0 @@
      -    m = re.match(code_re, text)
      

      补丁的内容可以很容易地浓缩为:

      +7,1 
      aaaaa
      -9,1 
      +10,1
      c= 1
      -15,1
      

      对于您自己的示例,压缩输出将是:

      -8,1
      +9,1
      print "The end"
      

      为了安全起见,为必须插入的行保留前导标记 ('>') 可能是个好主意。

      -8,1
      +9,1
      >print "The end"
      

      这更接近您的需要吗?

      这是一个简单的压缩函数。您必须编写自己的代码来应用该格式的补丁,但它应该很简单。

      def compact_a_unidiff(s):
          s = [l for l in s if l[0] in ('+','@')]
          result = []
          for l in s:
              if l.startswith('++'):
                  continue
              elif l.startswith('+'):
                  result.append('>'+ l[1:])
              else:
                  del_cmd, add_cmd = l[3:-3].split()
                  del_pair, add_pair = (c.split(',') for c in (del_cmd,add_cmd))
                  if del_pair[1]  != '0':
                      result.append(del_cmd)
                  if add_pair[1] != '0':
                      result.append(add_cmd)
          return result
      

      【讨论】:

      • 谢谢(请参阅上面对@Karl 的更新和注释)。我可能会处理简短的代码示例——一般为 10-20 行——所以我想我最好放弃 diff 概念。以及如何将差异存储在数据库 blob 中?序列化它,或者做一个 ''.join
      • 这一切都取决于目的。为什么要比较小文件?有多少文件?每个版本有多少次修订?如果您控制了差异引擎,那么存储格式可以比普通情况下的任何修订更紧凑(想想快速排序)。如果您想要使用/显示特定时间点的差异,那么对于您的小文档,您可以在需要时使用difflib 即时计算它们。请让我知道你的决定(我需要一个借口花一天时间将 JRCS.Diff 翻译成 Python)。
      • 根据这里的信息,我现在只存储整个代码块。如果我的项目开花结果,并且我有 100 万用户,那么我可以担心我的云托管磁盘空间成本,然后对其进行调整。如果你翻译成 Python,尽量让它不依赖 C,这样它就可以运行 Google App Engine(或两个版本)。
      • 在原始帖子中添加了“另一个更新”。我今天早上醒来想想旧的大型机工具是如何做到这一点的......
      • @NealWalters RCS/CVS 使用的格式也很紧凑,可以和很多库一起使用。我认为与difflib 多玩一点会很好。例如,您可以尝试使用具有零行上下文的统一格式并处理输出以使其更紧凑。有关示例,请参阅我的最新编辑。
      【解决方案3】:

      如果您只想进行更改,您想使用统一或上下文差异。您会看到更大的文件,因为它包含了它们共有的行。

      返回一个生成器的好处是整个东西不需要一次保存在内存中。这对于区分非常大的文件很有用。

      【讨论】:

      • 谢谢,我仍然对 context_diff 比 file2 大感到震惊。似乎差异会以它想要使用的任何内部语法说“在位置 235 处添加 'print "j=" + j '" 之类的东西。你能从 context_diff 做一个 .restore 吗?
      猜你喜欢
      • 2016-05-22
      • 2013-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-08
      • 2011-10-05
      • 1970-01-01
      • 2014-10-30
      相关资源
      最近更新 更多