Python Difflib差异和比较Ndiff

4
我想做的事情类似于我认为变更控制系统所做的事情,它们比较两个文件,并在每次文件更改时保存一个小的差异。我一直在阅读这个页面: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

----- SHOW CONTEXTDIFF -----




* 5,9 **

 print "HIJ"

 print "JKL"

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

    print "XYZ"

--- 5,9 ----

 print "HIJ"

 print "JKL"

 print "Hello World"

 print "XYZ"
  • print "The end"

另一个更新:

在大型机的源代码管理工具 Panvalet 和 Librarian 的旧时代,您可以像这样创建一个更改集:

++ADD 9
   print "j=" + j 

这意味着在第9行之后添加一行或多行。然后会出现像++REPLACE或++UPDATE这样的单词。http://www4.hawaii.gov/dags/icsd/ppmo/Stds_Web_Pages/pdf/it110401.pdf
3个回答

6
我还在试图弄清楚为什么许多difflib函数返回生成器而不是列表,这有什么优势呢?
好的,想一想 - 如果比较文件,则这些文件在理论上(并且在实践中)可能相当大 - 例如,将增量作为列表返回意味着将完整数据读入内存,这不是明智之举。
至于仅返回差异,那么使用生成器的另一个优点在于 - 只需迭代增量并保留您感兴趣的任何行即可。
如果您阅读Differ-样式增量的difflib文档,您将看到一个段落,其中写道:
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 来获取一个紧凑的 delta,它只显示更改内容。


4
Diffs必须包含足够的信息,以便将一个版本打补丁到另一个版本中,因此对于您对非常小的文档进行单行更改的实验,存储整个文档可能更便宜。
库函数返回迭代器,使得内存紧张或仅需要查看结果序列的客户端更容易。这在Python中是可以的,因为每个迭代器都可以用非常短的list(an_iterator)表达式转换为列表。
大多数差异都是在文本行上完成的,但也可以逐个字符进行比较,difflib就是这样做的。请查看difflib中对象的Differ类。
示例通常使用人性化输出,但差异在内部以更紧凑、更适合计算机的方式进行管理。此外,差异通常包含冗余信息(如要删除的行的文本),以使打补丁和合并更改更安全。如果您感觉舒适,可以通过自己的代码去除冗余。
我刚刚读到,difflib选择最少惊讶原则而不是最优解,这是我不会反驳的事情。有众所周知的算法可以快速生成最小变更集。
我曾经用大约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概念。那么你会如何将diff存储在数据库blob中?序列化它,还是使用''.join? - NealWalters
这完全取决于目的。你为什么要比较这些小文档?有多少个文档?每个文档有多少个修订版本?如果你控制了差异引擎,那么存储格式可以比平均情况下的任何修订版本都更紧凑(想想快速排序)。如果你想在特定时间点使用/显示差异,那么对于你的小文档,你可以在需要时使用difflib实时计算它们。请告诉我你的决定(我需要一个借口花一天时间将JRCS.Diff翻译成Python)。 - Apalala
根据这里的信息,我现在只打算存储整个代码块。如果我的项目蓬勃发展,我有了一百万用户,那么我可以担心云托管磁盘空间成本,然后进行调整。如果您将其转换为Python,请尽量使其不依赖于C,以便在Google App Engine上运行(或两个版本)。 - NealWalters
在原帖中添加了“另一个更新”。我今天早上醒来后想到了过去老的大型机工具是如何做到这一点的... - NealWalters
@NealWalters RCS/CVS使用的格式也非常紧凑,可以与许多库一起使用。我认为应该多尝试一下difflib。例如,您可以尝试使用零行上下文的统一格式,并处理输出以使其更紧凑。请参见我的最新编辑示例。 - Apalala
@Apalala 是的,这大概是我未来可能会走的方向。但就像我上面说的,让我们先看看我能否使网站成功,然后再考虑节省磁盘空间的问题。 - NealWalters

1

如果您只想查看更改内容,可以使用统一或上下文差异。由于包括它们共有的行,因此您会看到更大的文件。

返回生成器的优点是不需要一次性将整个内容保存在内存中。这对于比较非常大的文件非常有用。


谢谢,我仍然感到惊讶的是context_diff比file2还要大。它似乎应该说类似于“在位置235添加'print "j=" + j '”并使用任何内部语法来表达差异。而且你能从context_diff中进行.restore操作吗? - NealWalters

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