difflib — Comparar Secuencias¶
Propósito: | Compara secuencias, especialmente lineas de texto. |
---|
El módulo difflib
contiene herramientas para computar y trabajar con
diferencias entre secuencias. Es especialmente útil para comparando texto, e
incluye funciones que producen informes usando varios formatos de diferencia
comunes.
Los ejemplos en esta sección usarán todos los datos de prueba comunes en el
módulo 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()
Comparar cuerpos de texto¶
La clase Differ
funciona en secuencias de líneas de texto y produce
deltas legibles por humanos, o instrucciones de cambio, incluyendo
diferencias dentro de líneas individuales. La salida predeterminada producida
por Differ
es similar a la herramienta de línea de comandos diff
de
Unix. Incluye los valores original de entrada de ambas listas, incluyendo
valores comunes, y datos de marcado para indicar qué cambios fueron hechos.
- Las líneas con prefijo
-
estaban en la primera secuencia, pero no en la segunda. - Las líneas con el prefijo
+
estaban en la segunda secuencia, pero no la primera. - Si una línea tiene una diferencia incremental entre versiones, un línea extra
con el prefijo
?
se usa para resaltar el cambio dentro del nueva versión. - Si una línea no ha cambiado, se imprime con un espacio en blanco adicional en la columna izquierda para que esté alineado con la otra salida que puede tener diferencias.
Rompiendo el texto en una secuencia de líneas individuales antes de pasarlo a
compare()
produce un resultado más legible que pasando cadenas grandes.
import difflib
from difflib_data import *
d = difflib.Differ()
diff = d.compare(text1_lines, text2_lines)
print('\n'.join(diff))
El comienzo de ambos segmentos de texto en los datos de muestra es el mismo, por lo que la primera línea se imprime sin ninguna anotación adicional.
Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Integer eu lacus accumsan arcu fermentum euismod. Donec
La tercera línea de los datos ha sido cambiada para incluir una coma en el
texto modificado. Ambas versiones de la línea están impresas, con la
información extra en la línea 5 que muestra la columna donde se modificó el
texto, incluyendo el hecho de que se agregó el carácter ,
.
- pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis
+ pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis
? +
Las siguientes líneas del resultado muestran que se eliminó un espacio adicional.
- pharetra tortor. In nec mauris eget magna consequat
? -
+ pharetra tortor. In nec mauris eget magna consequat
A continuación, se realizó un cambio más complejo, reemplazando varias palabras en una frase.
- convalis. Nam sed sem vitae odio pellentesque interdum. Sed
? - --
+ convalis. Nam cras vitae mi vitae odio pellentesque interdum. Sed
? +++ +++++ +
La última oración del párrafo se modificó significativamente, por lo que la diferencia se representa eliminando la versión anterior y agregando la nueva.
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 función ndiff()
produce esencialmente el mismo resultado. El
procesamiento está específicamente diseñado para trabajar con datos de texto y
eliminando el «ruido» en la entrada.
Otros formatos de salida¶
Mientras que la clase Differ
muestra todas las líneas de entrada, un diff
unificado incluye solo las líneas modificadas y un poco de contexto. La
función unified_diff()
produce este tipo de salida.
import difflib
from difflib_data import *
diff = difflib.unified_diff(
text1_lines,
text2_lines,
lineterm='',
)
print('\n'.join(diff))
El argumento lineterm
se usa para indicar a unified_diff()
que omita
añadir nuevas líneas a las líneas de control que devuelve porque las línes de
entrada no las incluyen. Las líneas nuevas se agregan a todas las líneas
cuando son impresas. La salida debería ser familiar para los usuarios de
muchas herramientas populares de control de versiones.
$ 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. S
ed
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.
Usar context_diff()
produce una salida legible similar.
Datos basura¶
Todas las funciones que producen secuencias de diferencia aceptan argumentos para indicar qué líneas se deben ignorar y cuáles son los caracteres dentro de una línea que deben ser ignorados. Estos parámetros pueden ser utilizados por ejemplo para omitir los cambios de marcado o espacio en blanco en dos versiones de un archivo.
# This example is adapted from the source for difflib.py.
from difflib import SequenceMatcher
def show_results(match):
print(' a = {}'.format(match.a))
print(' b = {}'.format(match.b))
print(' size = {}'.format(match.size))
i, j, k = match
print(' A[a:a+size] = {!r}'.format(A[i:i + k]))
print(' B[b:b+size] = {!r}'.format(B[j:j + k]))
A = " abcd"
B = "abcd abcd"
print('A = {!r}'.format(A))
print('B = {!r}'.format(B))
print('\nWithout junk detection:')
s1 = SequenceMatcher(None, A, B)
match1 = s1.find_longest_match(0, len(A), 0, len(B))
show_results(match1)
print('\nTreat spaces as junk:')
s2 = SequenceMatcher(lambda x: x == " ", A, B)
match2 = s2.find_longest_match(0, len(A), 0, len(B))
show_results(match2)
Por defecto Differ
no ignora ninguna línea o caracteres explícitamente,
sino más bien confia en la capacidad de SequenceMatcher
para detectar
ruido. El valor por defecto para ndiff()
es ignorar los caracteres de
espacio y tabulación.
$ python3 difflib_junk.py
A = ' abcd'
B = 'abcd abcd'
Without junk detection:
a = 0
b = 4
size = 5
A[a:a+size] = ' abcd'
B[b:b+size] = ' abcd'
Treat spaces as junk:
a = 1
b = 0
size = 4
A[a:a+size] = 'abcd'
B[b:b+size] = 'abcd'
Comparar tipos arbitrarios¶
La clase SequenceMatcher
compara dos secuencias de cualquier tipo, siempre
y cuando los valores sean hashable. Utiliza un algoritmo para identificar los
bloques contiguos más largos de las secuencias, eliminando los valores «basura»
que no contribuyen a los datos reales.
La función get_opcodes()
devuelve una lista de instrucciones para modificar
la primera secuencia para que coincida con la segunda. Las instrucciones se
codifican como tuplas de cinco elementos, incluida una cadena de instrucción
(el «opcode», ve the table below) y dos pares de
índices de inicio y final en las secuencias (denotadas como i1
, i2
,
j1
, y j2
).
Opcode | Definición |
---|---|
'replace' |
Reemplaza a[i1:i2] con b[j1:j2] |
'delete' |
Elimina``a[i1:i2]`` en su totalidad |
'insert' |
Inserta b[j1:j2] en a[i1:i1] |
'equal' |
Las subsecuencias ya son iguales |
import difflib
s1 = [1, 2, 3, 5, 6, 4]
s2 = [2, 3, 5, 4, 6, 1]
print('Initial data:')
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('Remove {} from positions [{}:{}]'.format(
s1[i1:i2], i1, i2))
print(' before =', s1)
del s1[i1:i2]
elif tag == 'equal':
print('s1[{}:{}] and s2[{}:{}] are the same'.format(
i1, i2, j1, j2))
elif tag == 'insert':
print('Insert {} from s2[{}:{}] into s1 at {}'.format(
s2[j1:j2], j1, j2, i1))
print(' before =', s1)
s1[i1:i2] = s2[j1:j2]
elif tag == 'replace':
print(('Replace {} from s1[{}:{}] '
'with {} from s2[{}:{}]').format(
s1[i1:i2], i1, i2, s2[j1:j2], j1, j2))
print(' before =', s1)
s1[i1:i2] = s2[j1:j2]
print(' after =', s1, '\n')
print('s1 == s2:', s1 == s2)
Este ejemplo compara dos listas de enteros y usa get_opcodes()
para derivar
las instrucciones para convertir la lista original en la versión más nueva.
Las modificaciones se aplican en orden inverso para que los índices de lista
permanezcan precisos después de que los elementos se agreguen y eliminen.
$ python3 difflib_seq.py
Initial data:
s1 = [1, 2, 3, 5, 6, 4]
s2 = [2, 3, 5, 4, 6, 1]
s1 == s2: False
Replace [4] from s1[5:6] with [1] from s2[5:6]
before = [1, 2, 3, 5, 6, 4]
after = [1, 2, 3, 5, 6, 1]
s1[4:5] and s2[4:5] are the same
after = [1, 2, 3, 5, 6, 1]
Insert [4] from s2[3:4] into s1 at 4
before = [1, 2, 3, 5, 6, 1]
after = [1, 2, 3, 5, 4, 6, 1]
s1[1:4] and s2[0:3] are the same
after = [1, 2, 3, 5, 4, 6, 1]
Remove [1] from positions [0:1]
before = [1, 2, 3, 5, 4, 6, 1]
after = [2, 3, 5, 4, 6, 1]
s1 == s2: True
SequenceMatcher
funciona con clases personalizadas, así como tipos
incorporados, siempre que sean hashables.
Ver también
- Documentación de la biblioteca estándar para difflib
- «Pattern Matching: The Gestalt Approach» – Discusión de algoritmos similares por John W. Ratcliff y y D. E. Metzener publicada en Dr. Dobb’s Journal el Julio, 1988.