difflib - Confrontare Sequenze
Scopo: Il modulo difflib contiene strumenti per calcolare e lavorare con differenze tra sequenze. E' particolarmente utile per confrontare testo.
Il modulo difflib contiene strumenti per calcolare e lavorare con differenze tra sequenze. E' particolarmente utile per confrontare testo, ed include funzioni che producono report utilizzando parecchi comuni formati di rappresentazione delle differenze.
Gli esempi faranno tutti uso di questi comuni dati di test nel modulo difflib_data.py:
.
# difflib_data.py
text1 = """Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Integer eu lacus accumsan arcu fermentum euismod. Donec
pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis
pharetra tortor. In nec mauris eget magna consequat
convalis. Nam sed sem vitae odio pellentesque interdum. Sed
consequat viverra nisl. Suspendisse arcu metus, blandit quis,
rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy
molestie orci. Praesent nisi elit, fringilla ac, suscipit non,
tristique vel, mauris. Curabitur vel lorem id nisl porta
adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate
tristique enim. Donec quis lectus a justo imperdiet tempus."""
text1_lines = text1.splitlines()
text2 = """Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Integer eu lacus accumsan arcu fermentum euismod. Donec
pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis
pharetra tortor. In nec mauris eget magna consequat
convalis. Nam cras vitae mi vitae odio pellentesque interdum. Sed
consequat viverra nisl. Suspendisse arcu metus, blandit quis,
rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy
molestie orci. Praesent nisi elit, fringilla ac, suscipit non,
tristique vel, mauris. Curabitur vel lorem id nisl porta
adipiscing. Duis vulputate tristique enim. Donec quis lectus a
justo imperdiet tempus. Suspendisse eu lectus. In nunc."""
text2_lines = text2.splitlines()
Confrontare Parti di Testo
- Le righe prefissate con
-
indicano che si trovano nella prima sequenza, ma non nella seconda. - Le righe prefissate con
+
sono nella seconda sequenza, ma non nella prima. - Se una riga ha una differenza incrementale tra le versioni, viene utilizzata una riga aggiuntiva prefissata da
?
per evidenziare le modifiche con la nuova versione. - Se una riga non è cambiata, viene stampata con uno spazio aggiuntivo nella colonna di sinistra, per allinearla alle altre righe che potrebbero avere differenze.
Dividere il testo in una sequenza di singole righe prima di passarlo a compare()
fornisce un output più leggibile rispetto al passare stringhe molto grandi.
# difflib_differ.py
import difflib
from difflib_data import *
d = difflib.Differ()
diff = d.compare(text1_lines, text2_lines)
print('\n'.join(diff))
L'inizio di entrambi i segmenti nei dati di esempio è lo stesso, quindi la prima riga viene stampata senza ulteriori annotazioni.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec
La terza riga è stata cambiata per includere una virgola nel testo modificato. Entrambe le versioni della riga sono stampate, con una informazione aggiuntiva nella riga 5, che mostra la colonna dove il testo è stato modificato, incluso il fatto che il carattere ,
è stato aggiunto.
- pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis + pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis ? +
Nelle riga seguente si mostra che è stato rimosso uno spazio supplementare.
- pharetra tortor. In nec mauris eget magna consequat ? - + pharetra tortor. In nec mauris eget magna consequat
Successivamente è stato fatta una modifica più complessa, sostituendo diverse parole in una frase.
- convalis. Nam sed sem vitae odio pellentesque interdum. Sed ? - -- + convalis. Nam cras vitae mi vitae odio pellentesque interdum. Sed ? +++ +++++ +
L'ultima frase nel paragrafo è stata modificata in modo significativo, quindi la differenza viene rappresentata eliminando la vecchia versione ed aggiungendo la nuova.
consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta - adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate - tristique enim. Donec quis lectus a justo imperdiet tempus. + adipiscing. Duis vulputate tristique enim. Donec quis lectus a + justo imperdiet tempus. Suspendisse eu lectus. In nunc.
La funzione ndiff()
fornisce essenzialmente lo stesso risultato. Il processo è specificamente modellato per lavorare con dati di testo, eliminando il "rumore" dai dati in entrata.
Altri Formati di Output
Mentre la classe Differ
mostra tutte le righe in input, esiste una funzione, unified_diff()
che mostra solo le righe modificate, assieme ad un poco di contesto e produce questo tipo di output.
# difflib_unified.py
import difflib
from difflib_data import *
diff = difflib.unified_diff(
text1_lines,
text2_lines,
lineterm='',
)
print('\n'.join(list(diff)))
L'argomento lineterm viene usato per dire a unified_diff()
di evitare di aggiungere nuove righe a quelle di controllo che ritorna in quanto le righe in input non le comprendono. Le righe nuove sono aggiunte a tutte le righe quando sono stampate. L'output dovrebbe risultare familiare agli utilizzatori di molti strumenti di controllo di versione.
$ python3 difflib_unified.py --- +++ @@ -1,11 +1,11 @@ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec -pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis -pharetra tortor. In nec mauris eget magna consequat -convalis. Nam sed sem vitae odio pellentesque interdum. Sed +pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis +pharetra tortor. In nec mauris eget magna consequat +convalis. Nam cras vitae mi vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta -adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate -tristique enim. Donec quis lectus a justo imperdiet tempus. +adipiscing. Duis vulputate tristique enim. Donec quis lectus a +justo imperdiet tempus. Suspendisse eu lectus. In nunc.
L'utilizzo di context_diff()
produce un output parimenti leggibile.
Dati da Ignorare
Tutte le funzioni che producono sequenze di differenze accettano argomenti per indicare quali righe dovrebbero essere ignorate e quali caratteri all'interno di una riga dovrebbero essere ignorati. Questi parametri possono essere usati per saltare modifiche a testo di marcatura oppure modifiche relative a spazi e/o caratteri di controllo nelle due versioni di un file, per esempio.
# difflib_junk.py
# Questo esempio è un adattamento dal sorgente di difflib.py.
from difflib import SequenceMatcher
def show_results(s):
i, j, k = s.find_longest_match(0, 5, 0, 9)
print(' i = {}'.format(i))
print(' j = {}'.format(j))
print(' k = {}'.format(k))
print(' A[i:i+k] = {!r}'.format(A[i:i + k]))
print(' B[j:j+k] = {!r}'.format(B[j:j + k]))
A = " abcd"
B = "abcd abcd"
print('A = {!r}'.format(A))
print('B = {!r}'.format(B))
print('\nSenza rilevamento di caratteri da ignorare:')
show_results(SequenceMatcher(None, A, B))
print('\nTratta gli spazi come caratteri da ignorare:')
show_results(SequenceMatcher(lambda x: x == " ", A, B))
Il comportamento predefinito di Differ
è quello di non ignorare alcuna riga o carattere esplicitamente, ma appoggiarsi alle capacità di SequenceMatcher
per individuare il "rumore". Nella modalità predefinita di ndiff()
vengono ignorati spazi e caratteri di tabulazione.
$ python3 difflib_junk.py A = ' abcd' B = 'abcd abcd' Senza rilevamento di caratteri da ignorare: i = 0 j = 4 k = 5 A[i:i+k] = ' abcd' B[j:j+k] = ' abcd' Tratta gli spazi come caratteri da ignorare: i = 1 j = 0 k = 4 A[i:i+k] = 'abcd' B[j:j+k] = 'abcd'
Confrontare Tipi Arbitrari
La classe SequenceMatcher
confronta due sequenze di qualsiasi tipo, fintanto che gli elementi possono essere utilizzati in funzioni di hash. La classe utilizza un algoritmo per identificare il blocco di corrispondenza contiguo più lungo dalle sequenze, eliminando valori "spazzatura" che non contribuiscono ai dati reali.
# difflib_seq.py
import difflib
from difflib_data import *
s1 = [1, 2, 3, 5, 6, 4]
s2 = [2, 3, 5, 4, 6, 1]
print('Dati iniziali:')
print('s1 =', s1)
print('s2 =', s2)
print('s1 == s2:', s1 == s2)
print()
matcher = difflib.SequenceMatcher(None, s1, s2)
for tag, i1, i2, j1, j2 in reversed(matcher.get_opcodes()):
if tag == 'delete':
print('Elimina {} dalle posizioni [{}:{}]'.format(
s1[i1:i2], i1, i2))
del s1[i1:i2]
elif tag == 'equal':
print('s1[{}:{}] e s2[{}:{}] sono uguali'.format(
i1, i2, j1, j2))
elif tag == 'insert':
print('Inserisce {} da s2[{}:{}] in s1 a {}'.format(
s2[j1:j2], j1, j2, i1))
s1[i1:i2] = s2[j1:j2]
elif tag == 'replace':
print(('Sostituisce {} da s1[{}:{}] '
'con {} da s2[{}:{}]').format(
s1[i1:i2], i1, i2, s2[j1:j2], j1, j2))
s1[i1:i2] = s2[j1:j2]
print(' s1 =', s1)
print('s1 == s2:', s1 == s2)
Questo esempio confronta due liste di interi ed usa get_opcodes()
per derivare le istruzioni per convertire la lista originale nella nuova versione. Le modifiche sono applicate in ordine inverso in modo che gli indici di lista rimangano precisi dopo l'aggiunta e la rimozione di elementi.
$ python3 difflib_seq.py Dati iniziali: s1 = [1, 2, 3, 5, 6, 4] s2 = [2, 3, 5, 4, 6, 1] s1 == s2: False Sostituisce [4] da s1[5:6] con [1] da s2[5:6] s1 = [1, 2, 3, 5, 6, 1] s1[4:5] e s2[4:5] sono uguali s1 = [1, 2, 3, 5, 6, 1] Inserisce [4] da s2[3:4] in s1 a 4 s1 = [1, 2, 3, 5, 4, 6, 1] s1[1:4] e s2[0:3] sono uguali s1 = [1, 2, 3, 5, 4, 6, 1] Elimina [1] dalle posizioni [0:1] s1 = [2, 3, 5, 4, 6, 1] s1 == s2: True
SequenceMatcher funziona con classi personalizzate, così come per i tipi built-in, fintanto che gli elementi possono essere utilizzati in funzioni di hash.
Vedere anche:
- difflib
- La documentazione della libreria standard per questo modulo
- Pattern Matching: The Gestalt Approach
- Discussione su di un algoritmo simile di John W. Ratcliff e D. E. Metzener pubblicato nel Dr. Dobb's Journal del luglio 1988 (in inglese).