Python Difflib delta e confronta Ndiff
Domanda
che stavo cercando di fare qualcosa di simile a quello che io credo sistemi di controllo cambiamento fanno, confrontare due file, e salvare una piccola diff ogni volta che il file cambia. Ho letto questa pagina: http://docs.python.org/library/difflib.html e non è affondare nella mia testa a quanto pare.
I stava cercando di ricreare questo in maniera un po semplice programma riportato di seguito, ma la cosa che mi sembra mancare è che i Delta contengono almeno tanto quanto il file originale, e altro ancora.
Non è possibile raggiungere solo le modifiche puri?
Il motivo che mi chiedo è ovvio si spera - per risparmiare spazio su disco
.
Solo potessi salvare l'intero pezzo di codice ogni volta, ma sarebbe meglio risparmiare codice corrente una volta, poi piccoli diff dei cambiamenti.
Inoltre sto ancora cercando di capire il motivo per cui molte funzioni difflib restituiscono un generatore invece di una lista, qual è il vantaggio c'è?
lavorerà difflib per me - o ho bisogno di trovare un pacchetto più professionale con più funzioni?
# 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
Grazie!
UPDATE:
contextDiffSeq = difflib.context_diff(lines1, lines2)
contextDiffList = list(contextDiffSeq)
print "\n ----- SHOW CONTEXTDIFF ----- \n"
for i, line in enumerate(contextDiffList):
print line
----- MOSTRA CONTEXTDIFF -----
* 5,9 **
print "HIJ" print "JKL" print "Hello World"
stampare "j =" + j
print "XYZ"
--- 5,9 ----
print "HIJ" print "JKL" print "Hello World" print "XYZ"
- stampare "La fine"
Un altro aggiornamento:
Ai vecchi tempi di Panvalet un bibliotecario, strumenti di gestione di origine per il mainframe, è possibile creare un insieme di modifiche in questo modo:
++ADD 9
print "j=" + j
Il che significa semplicemente aggiungere una riga (o righe) dopo la linea 9. Poi le parole c'è parola come UPDATE ++ sostituire o ++. http://www4.hawaii.gov/dags/icsd/ppmo /Stds_Web_Pages/pdf/it110401.pdf
Soluzione
Diffs deve contenere informazioni sufficienti per consentire di patch per una versione in un altro, quindi sì, per l'esperimento di un cambiamento a linea singola per un piccolo documento, l'archiviazione dei documenti integrali potrebbe essere più conveniente.
Le funzioni di libreria restituiscono iteratori per rendere più facile per i clienti che sono stretti in memoria o solo bisogno di guardare parte della sequenza risultante. Va bene in Python, perché ogni iteratore può essere convertito in un elenco con una breve espressione list(an_iterator)
.
La maggior parte di differenziazione viene effettuata su linee di testo, ma è possibile scendere al char-by-char, e difflib
lo fa. Date un'occhiata alla Differ
classe di oggetti in difflib
.
Gli esempi sopra tutti l'uscita human-friendly posto uso, ma il diff sono gestiti internamente in un modo molto più compatto, computer-friendly. Inoltre, diff di solito contengono informazioni ridondanti (come il testo di una linea da cancellare) per rendere patch e fusione modifiche al sicuro. La ridondanza può essere rimosso con il proprio codice, se ti trovi bene con quello.
Ho appena letto che opta per difflib
meno sorpresa a favore di ottimalità, che è qualcosa che non voglio argomentare contro. Ci sono noto algoritmi che sono veloci a produrre un insieme minimo di cambiamenti.
Una volta ho codificato un motore diffing generico insieme con uno degli algoritmi ottimali in circa 1250 linee di Java ( i CCR ). Funziona per qualsiasi sequenza di elementi che possono essere paragonato per l'uguaglianza. Se si vuole costruire la vostra soluzione, penso che una traduzione / reimplementazione dei CCR non dovrebbe prendere più di 300 linee di Python.
Elaborazione dell'output prodotto da difflib
per renderlo più compatto è anche un'opzione. Questo è un esempio da un piccolo file con tre cambi (un'aggiunta, un cambiamento, e una soppressione):
---
+++
@@ -7,0 +7,1 @@
+aaaaa
@@ -9,1 +10,1 @@
-c= 0
+c= 1
@@ -15,1 +16,0 @@
- m = re.match(code_re, text)
Quello che il cerotto dice può essere facilmente condensato a:
+7,1
aaaaa
-9,1
+10,1
c= 1
-15,1
Per il vostro esempio l'uscita condensata potrebbe essere:
-8,1
+9,1
print "The end"
Per motivi di sicurezza, lasciando in un marcatore leader ( '>') per le linee che devono essere inseriti potrebbe essere una buona idea.
-8,1
+9,1
>print "The end"
E 'questo più vicino a quello che ti serve?
Questa è una semplice funzione per fare la compattazione. Dovrete scrivere il proprio codice per applicare la patch in questo formato, ma dovrebbe essere semplice.
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
Altri suggerimenti
Sono anche ancora cercando di capire il motivo per cui molte funzioni restituiscono un difflib generatore invece di un elenco, che cosa è il vantaggio c'è?
Bene, pensate per un secondo - se si confrontano i file, questi file possono in teoria (e saranno in pratica) è abbastanza grande - la restituzione del Delta come una lista, per exampe, mezzi di lettura i dati completi in memoria , che non è una cosa intelligente da fare.
Per quanto riguarda solo restituendo la differenza, beh, c'è un altro vantaggio nell'utilizzo di un generatore -. Basta scorrere i delta e mantenere tutto ciò che le linee che ti interessa
Se leggete la difflib documentazione per Differ - delta di stile, si vedrà un paragrafo che recita:
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
Quindi, se desideri solo differenze, si può facilmente filtrare quelli fuori utilizzando str.startswith
È inoltre possibile utilizzare difflib.context_diff
per ottenere un delta compatto che mostra solo le modifiche.
Si desidera utilizzare la diff unificata o il contesto se si desidera solo le modifiche. Stai vedendo file più grandi, perché comprende le linee che hanno in comune.
Il vantaggio di restituire un generatore è che l'intera cosa non ha bisogno di essere tenuto in memoria in una sola volta. Questo può essere utile per diffing file di grandi dimensioni.