difflib — Funciones auxiliares para calcular deltas

Código fuente: Lib/difflib.py


Este módulo proporciona clases y funciones para comparar secuencias. Se puede utilizar, por ejemplo, para comparar archivos y puede producir información sobre diferencias de archivo en varios formatos, incluidos HTML y contexto y diferencias unificadas. Para comparar directorios y archivos, consulte también el módulo filecmp.

class difflib.SequenceMatcher

Esta es una clase flexible para comparar pares de secuencias de cualquier tipo, siempre que los elementos de la secuencia sean hashable. El algoritmo básico es anterior, y un poco mas agradable, que el publicado a fines de los 80” por Ratcliff y Obershelp, bajo el nombre hiperbólico de «gestalt pattern matching». La idea es encontrar la subsecuencia coincidente contigua mas larga que no contenga elementos «no deseados»; estos elementos «no deseados» son aquellos que no son de interés por algún motivo, como ser lineas en blanco o espacios. (El tratamiento de elementos no deseados es una extensión al algoritmo de Ratcliff y Obershelp). La misma idea se aplica recursivamente a las partes de la secuencia a la derecha e izquierda de cada subsecuencia correspondiente. Esto no proporciona secuencias de edición mínimas, pero tiende a producir coincidencias que «parecen correctas» a las personas.

Complejidad temporal: En el peor de los casos el algoritmo Ratcliff-Obershelp básico es de complejidad cúbica y de complejidad temporal cuadrática en el caso esperado. SequenceMatcher es de complejidad temporal cuadrática en el peor de los casos y el comportamiento del caso esperado depende de manera complicada de cuántos elementos tienen en común las secuencias; en el mejor de los casos la complejidad temporal es lineal.

Heurística automática de elementos no deseados: SequenceMatcher implementa un método heurístico que identifica automáticamente a ciertos elementos como no deseados. El método heurístico consiste en contar cuantas veces aparece cada elemento en la secuencia. Si las apariciones del duplicado de un elemento (después del primero) contabilizan mas del 1% de la secuencia, y a su vez la secuencia contiene mas de 200 elementos, este es identificado como «popular» y es considerado no deseado. Este método heurístico puede desactivarse estableciendo el argumento autojunk como False al crear la clase SequenceMatcher.

Nuevo en la versión 3.2: El parámetro autojunk.

class difflib.Differ

Esta clase se utiliza para comparar secuencias de lineas de texto y producir diferencias o deltas en una forma legible por humanos. Differ usa SequenceMatcher tanto para comparar secuencias de lineas, como para comparar secuencias de caracteres entre lineas similares.

Cada linea de un delta de Differ comienza con un código de dos letras:

Código

Significado

'- '

línea única para la secuencia 1

'+ '

línea única para la secuencia 2

'  '

línea común a ambas secuencias

'? '

línea ausente en todas las secuencias de entrada

Las líneas que empiezan con “?” intentan guiar al ojo hacia las diferencias intralíneas, y no estuvieron presentes en ninguna de las secuencias de entrada. Estas líneas pueden ser confusas si la secuencia contiene caracteres de tabulación.

class difflib.HtmlDiff

Esta clase puede ser usada para crear una tabla HTML (o un archivo HTML completo que contenga la tabla) mostrando comparaciones lado a lado y linea por linea del texto, con cambios interlineales e intralineales. La tabla se puede generar en modo de diferencia completa o contextual.

El constructor de esta clase es:

__init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)

Inicializa una instancia de HtmlDiff.

tabsize es un argumento por palabra clave opcional para especificar el espaciado de tabulación. Su valor predeterminado es 8.

wrapcolumn es un argumento por palabra clave opcional para especificar el número de columnas donde las lineas serán divididas y ajustadas al ancho de columna, su valor por defecto es None, donde las lineas no son ajustadas.

linejunk y charjunk son argumentos por palabra clave opcionales que serán pasados a ndiff() (que es utilizado por HtmlDiff para generar las diferencias lado a lado en HTML). Refiérase a la documentación de ndiff() para conocer los detalles y valores por defecto de sus argumentos.

Los siguientes métodos son públicos:

make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5, *, charset='utf-8')

Compara fromlines y tolines (listas de cadenas de texto) y retorna una cadena de caracteres que representa un archivo HTML completo que contiene una tabla mostrando diferencias línea por línea del texto con cambios interlineales e intralineales resaltados.

fromdesc y todesc son argumentos por palabra clave opcionales para especificar los encabezados de las columnas desde fromdesc hasta todesc en el archivo (ambas cadenas están vacías por defecto).

context y numlines son parámetros opcionales. Establezca context como True para mostrar diferencias contextuales, de lo contrario su valor por defecto es False que muestra los archivos completos. El valor por defecto de numlines es 5. Cuando context es True, numlines controla el número de lineas de contexto que rodean las diferencias resaltadas. Cuando context es False, numlines controla el número de líneas que se muestran antes de una diferencia resaltada cuando se usan los hipervínculos «next» (un valor de cero produce que los hipervínculos «next» ubiquen el siguiente resaltado en la parte superior del navegador, sin ningún contexto principal).

Nota

fromdesc y todesc se interpretan como HTML no escapado y se deben escapar correctamente si los datos son recibidos de fuentes no confiables.

Distinto en la versión 3.5: Se agregó el argumento sólo de palabra clave charset. La codificación de caracteres por defecto para documentos HTML se cambió de 'ISO-8859-1' a 'utf-8'.

make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)

Compara fromlines y tolines (listas de cadenas de texto) y retorna una cadena de caracteres que representa una tabla HTML mostrando comparaciones lado a lado y línea por línea del texto con cambios interlineales e intralineales.

Los argumentos para este método son los mismos que los del método make_file().

Tools/scripts/diff.py es una herramienta de línea de comandos para esta clase y contiene un buen ejemplo sobre su uso.

difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')

Compara a y b (listas de cadenas de texto); retorna un delta (un generator que genera las lineas delta) en formato de diferencias de contexto.

El formato de diferencias de contexto es una forma compacta de mostrar solamente las líneas que fueron modificadas y algunas líneas adicionales de contexto. Los cambios son mostrados utilizando el estilo antes/después. El número de líneas de contexto es determinado por n, cuyo valor por defecto es 3.

Por defecto, las líneas de control (aquellas que comienzan con *** o ---) son creadas con una línea nueva. Esto es de ayuda para que las entradas creadas por io.IOBase.readlines() generen diferencias que puedan ser utilizadas con io.IOBase.writelines() ya que ambas, la entrada y la salida, tienen líneas nuevas al final.

Para entradas que no tienen nuevas líneas finales, establezca el argumento lineterm como "" de forma que la salida sea uniforme y libre de nuevas líneas.

El formato de diferencias de contexto normalmente tiene un encabezado para nombres de archivos y tiempos de modificaciones. Alguno o todos estos debe ser especificado utilizando las cadenas de texto para fromfile, tofile, fromfiledate y tofiledate. Los tiempos de modificación son normalmente expresados en formato ISO 8601. Si no es especificado las cadenas por defecto son espacios en blanco.

>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py', tofile='after.py'))
*** before.py
--- after.py
***************
*** 1,4 ****
! bacon
! eggs
! ham
  guido
--- 1,4 ----
! python
! eggy
! hamster
  guido

Vea Una interfaz de línea de comandos para difflib para un ejemplo mas detallado.

difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)

Retorna una lista de las mejores coincidencias «lo suficientemente buenas». word es una secuencia para la cual coincidencias cercanas son deseadas (usualmente una cadena de texto), y possibilities es una lista de secuencias contra la cual se compara word (comúnmente una lista de cadenas de caracteres).

Argumento opcional n (por defecto 3) es el máximo número de coincidencias cercanas a retornar; n debe ser mayor que 0.

Argumento opcional cutoff (por defecto 0.6) es un flotante en el rango [0, 1]. Las posibilidades que no alcanzan un puntaje al menos similar al de word son ignoradas.

Las mejores (no mas de n) coincidencias entre las posibilidades son retornadas en una lista, ordenadas por similitud de puntaje, las mas similares primero.

>>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'])
['apple', 'ape']
>>> import keyword
>>> get_close_matches('wheel', keyword.kwlist)
['while']
>>> get_close_matches('pineapple', keyword.kwlist)
[]
>>> get_close_matches('accept', keyword.kwlist)
['except']
difflib.ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK)

Compara a y b (listas de cadenas de texto); retorna un delta del estilo Differ (un generator que genera los deltas de las líneas).

Parámetros de palabra clave opcional linejunk y charjunk son funciones de filtrado (o None):

linejunk: Una función que acepta una sola cadena de caracteres como argumento, y retorna verdadero si la cadena de texto es un elemento no deseado, o falso si no lo es. Su valor por defecto es None. Hay también una función a nivel del módulo IS_LINE_JUNK(), que filtra líneas sin caracteres visibles, excepto como mucho un carácter de libra ('#') – de cualquier forma la clase subyacente SequenceMatcher realiza un análisis dinámico sobre cuáles lineas son tan frecuentes como para constituir ruido, y esto usualmente funciona mejor que utilizando esta función.

charjunk: Una función que acepta un carácter (una cadena de caracteres de longitud 1) como argumento, y retorna True si el carácter es un elemento no deseado, o False si no lo es. El valor por defecto es una función a nivel del módulo IS_CHARACTER_JUNK(), que filtra caracteres de espacios en blanco (un espacio en blanco o tabulación; es una mala idea incluir saltos de lineas en esto!)

Tools/scripts/ndiff.py es una interfaz de línea de comandos para esta función.

>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
...              'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> print(''.join(diff), end="")
- one
?  ^
+ ore
?  ^
- two
- three
?  -
+ tree
+ emu
difflib.restore(sequence, which)

Retorna uno de las dos secuencias que generaron un delta.

Dada una sequence producida por Differ.compare() o ndiff(), extrae las líneas originadas por el archivo 1 o 2 (parámetro which), quitando los prefijos de la línea.

Ejemplo:

>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
...              'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> diff = list(diff) # materialize the generated delta into a list
>>> print(''.join(restore(diff, 1)), end="")
one
two
three
>>> print(''.join(restore(diff, 2)), end="")
ore
tree
emu
difflib.unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')

Compara a y b (listas de cadenas de caracteres); retorna un delta (un generator que genera los delta de líneas) en formato de diferencias unificado.

Las diferencias unificadas son una forma compacta de mostrar sólo las líneas que presentan cambios y algunas líneas de contexto adicionales. Los cambios son mostrados en una sola línea (en lugar de bloques separados antes y después). El número de líneas de contexto se establece mediante n, cuyo valor por defecto es tres.

Por defecto, las líneas de control de diferencias (aquellas con ---, +++, o @@) son creadas con un salto de línea nuevo. Esto es de ayuda para que las entradas creadas por io.IOBase.readlines() generen diferencias que puedan ser utilizadas con io.IOBase.writelines() ya que ambas, la entrada y la salida, tienen líneas nuevas al final.

Para entradas que no tienen nuevas líneas finales, establezca el argumento lineterm como "" de forma que la salida sea uniforme y libre de nuevas líneas.

El formato de diferencias de contexto normalmente tiene un encabezado para nombres de archivos y tiempos de modificaciones. Alguno o todos estos debe ser especificado utilizando las cadenas de texto para fromfile, tofile, fromfiledate y tofiledate. Los tiempos de modificación son normalmente expresados en formato ISO 8601. Si no es especificado las cadenas por defecto son espacios en blanco.

>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(unified_diff(s1, s2, fromfile='before.py', tofile='after.py'))
--- before.py
+++ after.py
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
 guido

Vea Una interfaz de línea de comandos para difflib para un ejemplo mas detallado.

difflib.diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n')

Compara a y b (listas de objetos de bytes) usando dfunc; produce una secuencia de líneas delta (también bytes) en el formato retornado por dfunc. dfunc debe ser invocable, comúnmente cualquiera de unified_diff() o context_diff().

Permite comparar datos con codificación desconocida o inconsistente. Todas las entradas, excepto n, deben ser objetos de bytes, no cadenas de texto. Funciona convirtiendo sin pérdidas todas las entradas (excepto n) a cadenas de texto, e invoca dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm). La salida de dfunc es entonces convertida nuevamente a bytes, de forma que las líneas delta que son recibidas tienen la misma codificación desconocida/inconsistente que a y b.

Nuevo en la versión 3.5.

difflib.IS_LINE_JUNK(line)

Retorna True para líneas que deben ser ignoradas. La línea line es ignorada si line es un espacio vacío o contiene un solo '#', en cualquier otro caso no es ignorado. Es usado como valor por defecto para el parámetro linejunk por ndiff() en versiones anteriores.

difflib.IS_CHARACTER_JUNK(ch)

Retorna True para los caracteres que deben ser ignorados. El carácter ch es ignorado si ch es un espacio en blanco o tabulación, en cualquier otro caso no es ignorado. Es utilizado como valor por defecto para el parámetro charjunk en ndiff().

Ver también

Pattern Matching: The Gestalt Approach

Discusión de un algoritmo similar por John W. Ratcliff y D. E. Metzener. Esto fue publicado en Dr. Dobb’s Journal en Julio de 1988.

Objetos SequenceMatcher

La clase SequenceMatcher tiene este constructor:

class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)

El argumento opcional isjunk debe ser None (que es su valor por defecto) o una función de un solo argumento que reciba un elemento de la secuencia y retorne verdadero si y solo si el elemento es no deseado y deba ser ignorado. Pasar el argumento isjunk como None es equivalente a pasar lambda x: False; en otras palabras, ningún elemento es ignorado. Por ejemplo, pasar:

lambda x: x in " \t"

si se están comparando líneas como secuencias de caracteres, y no se quiere sincronizar en espacios blancos o tabulaciones.

Los argumentos opcionales a y b son las secuencias a comparar; ambos tienen como valor por defecto una cadena de texto vacía. Los elementos de ambas secuencias deben ser hashable.

El argumento opcional autojunk puede ser usado para deshabilitar la heurística automática de elementos no deseados.

Nuevo en la versión 3.2: El parámetro autojunk.

Los objetos SequenceMatcher reciben tres atributos: bjunk es el conjunto de elementos de b para los cuales isjunk es True; bpopular es el set de elementos que no son basura considerados populares por la heurística (si no es que fue deshabilitada); b2j es un diccionario que mapea elementos de b a una lista de posiciones donde estos ocurren. Los tres atributos son reseteados cuando b es reseteado mediante set_seqs() o set_seq2().

Nuevo en la versión 3.2: Los atributos bjunk y bpopular.

Los objetos SequenceMatcher tienen los siguientes métodos:

set_seqs(a, b)

Establece las dos secuencias a ser comparadas.

SequenceMatcher calcula y almacena información detallada sobre la segunda secuencia, con lo cual si quieres comparar una secuencia contra muchas otras, usa set_seq2() para establecer la secuencia común una sola vez y llamar set_seq1() repetidamente, una vez por cada una de las otras secuencias.

set_seq1(a)

Establece la primer secuencia a ser comparada. La segunda secuencia a ser comparada no es modificada.

set_seq2(b)

Establece la segunda secuencia a ser comparada. La primera secuencia a ser comparada no es modificada.

find_longest_match(alo=0, ahi=None, blo=0, bhi=None)

Encuentra el bloque de coincidencia mas largo en a[alo:ahi] y b[blo:bhi].

Si isjunk fue omitido o es None, find_longest_match() retorna (i, j, k) tal que a[i:i+k] es igual a b[j:j+k], donde alo <= i <= i+k <= ahi y blo <= j <= j+k <= bhi. Para todo (i', j', k') cumpliendo esas condiciones, las condiciones adicionales k >= k', i <= i', y si i == i', j <= j' también se cumplen. En otras palabras, de todos los bloques coincidentes máximos, retorna aquel que comienza antes en a, y de todos esos bloques coincidentes máximos que comienzan antes en a, retorna aquel que comienza antes en b.

>>> s = SequenceMatcher(None, " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
Match(a=0, b=4, size=5)

Si se porporcionó isjunk, primero se determina el bloque coincidente mas largo como fue indicado anteriormente, pero con la restricción adicional de que no aparezca ningún elemento no deseado en el bloque. Entonces, ese bloque se extiende tan lejos como sea posible haciendo coincidir (solo) elementos no deseados de ambos lados. Por lo tanto, el bloque resultante nunca hará coincidir ningún elemento no deseado, excepto que un elemento no deseado idéntico pase a ser adyacente a una coincidencia interesante.

Este es el mismo ejemplo que el mostrado anteriormente, pero considerando elementos en blanco como no deseados. Esto previene que ' abcd' sea coincidente con 'abcd' en el final de la segunda secuencia directamente. En cambio, sólo el 'abcd' puede coincidir, y coincide con el 'abcd' que se encuentre mas a la izquierda en la segunda secuencia:

>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
Match(a=1, b=0, size=4)

Si no coincide ningún bloque, esto retorna (alo, blo, 0).

Este método retorna un named tuple Match(a, b, size).

Distinto en la versión 3.9: Se agregaron argumentos predeterminados.

get_matching_blocks()

Retorna una lista de triplas (tuplas de tres elementos) describiendo subsecuencias coincidentes no superpuestas. Cada tripla sigue el formato (i, j, n), y significa que a[i:i+n] == b[j:j+n]. Las triplas son monótonamente crecientes en i y j.

La última tripla es un objeto ficticio (dummy), y tiene el valor (len(a), len(b), 0). Es la única tripla con n == 0. Si (i, j, n) y (i', j', n') son triplas adyacentes en la lista, y la segunda no es el último elemento de la lista, entonces i+n < i' o j+n < j'; en otras palabras, las triplas adyacentes describen bloques iguales no adyacentes.

>>> s = SequenceMatcher(None, "abxcd", "abcd")
>>> s.get_matching_blocks()
[Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]
get_opcodes()

Retorna una lista de quíntuplas (tuplas de cinco elementos) describiendo como convertir a en b. Cada tupla tiene la forma (tag, i1, i2, j1, j2). En la primer tupla se cumple que i1 == j1 == 0, y las tuplas restantes tienen i1 igual al i2 de la tupla precedente, y de igual manera, j1 igual al j2 de la tupla anterior.

Los valores de tag son cadenas de caracteres, con el siguiente significado:

Valor

Significado

'replace'

a[i1:i2] debe ser reemplazado por b[j1:j2].

'delete'

a[i1:i2] debe ser eliminado. Nótese que en este caso j1 == j2.

'insert'

b[j1:j2] debe ser insertado en a[i1:i1]. Nótese que en este caso i1 == i2.

'equal'

a[i1:i2] == b[j1:j2] (las subsecuencias son iguales).

Por ejemplo:

>>> a = "qabxcd"
>>> b = "abycdf"
>>> s = SequenceMatcher(None, a, b)
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
...     print('{:7}   a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(
...         tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))
delete    a[0:1] --> b[0:0]      'q' --> ''
equal     a[1:3] --> b[0:2]     'ab' --> 'ab'
replace   a[3:4] --> b[2:3]      'x' --> 'y'
equal     a[4:6] --> b[3:5]     'cd' --> 'cd'
insert    a[6:6] --> b[5:6]       '' --> 'f'
get_grouped_opcodes(n=3)

Retorna un generator de grupos de hasta n líneas de contexto.

Empezando con los grupos retornados por get_opcodes(), este método separa grupos con cambios menores y elimina los rangos intermedios que no tienen cambios.

Los grupos son retornados en el mismo formato que get_opcodes().

ratio()

Retorna una medida de la similitud de las secuencias como un flotante en el rango [0, 1].

Donde T es el número total de elementos en ambas secuencias y M es el número de coincidencias, esto es 2.0*M / T. Nótese que esto es 1.0 si las secuencias son idénticas y 0.0 si no tienen nada en común.

Esto es computacionalmente costoso si get_matching_blocks() o get_opcodes() no fueron ejecutados, in tal caso deberías considerar primero quick_ratio() o real_quick_ratio() para obtener un límite superior.

Nota

Precaución: El resultado de una llamada a ratio() puede depender del orden de los argumentos. Por ejemplo:

>>> SequenceMatcher(None, 'tide', 'diet').ratio()
0.25
>>> SequenceMatcher(None, 'diet', 'tide').ratio()
0.5
quick_ratio()

Retorna un límite superior en ratio() relativamente rápido.

real_quick_ratio()

Retorna un límite superior en ratio() muy rápido.

Los tres métodos que retornan la proporción de coincidencias con el total de caracteres pueden dar diferentes resultados debido a los distintos niveles de aproximación, a pesar de que quick_ratio() y real_quick_ratio() son siempre al menos tan grandes como ratio():

>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0

SequenceMatcher Ejemplos

Este ejemplo compara dos cadenas de texto, considerando los espacios en blanco como caracteres no deseados:

>>> s = SequenceMatcher(lambda x: x == " ",
...                     "private Thread currentThread;",
...                     "private volatile Thread currentThread;")

ratio() retorna un flotante en el rango [0, 1], cuantificando la similitud entre las secuencias. Siguiendo la regla del pulgar, un ratio() por encima de 0.6 significa que las secuencias son coincidencias cercanas:

>>> print(round(s.ratio(), 3))
0.866

Si solamente estás interesado en cuándo las secuencias coinciden, get_matching_blocks() es útil:

>>> for block in s.get_matching_blocks():
...     print("a[%d] and b[%d] match for %d elements" % block)
a[0] and b[0] match for 8 elements
a[8] and b[17] match for 21 elements
a[29] and b[38] match for 0 elements

Nótese que la última tupla retornada por get_matching_blocks() es siempre un objeto ficticio (dummy), (len(a), len(b), 0), y este es el único caso en el cual el último elemento de la tupla (el número de elementos coincidentes) es 0.

Si quieres saber como cambiar la primer secuencia con la segunda, usa get_opcodes():

>>> for opcode in s.get_opcodes():
...     print("%6s a[%d:%d] b[%d:%d]" % opcode)
 equal a[0:8] b[0:8]
insert a[8:8] b[8:17]
 equal a[8:29] b[17:38]

Ver también

Objetos Differ

Nótese que los deltas generados por Differ no dicen ser diferencias mínimas. Todo lo contrario, las diferencias mínimas suelen ser contra-intuitivas, ya que se sincronizan en cualquier lugar posible, a veces coinciden accidentalmente con 100 páginas de diferencia. Restringiendo los puntos de sincronización a coincidencias contiguas se preserva cierta noción de cercanía, con el costo adicional de producir diferencias mas largas.

La clase Differ tiene el siguiente constructor:

class difflib.Differ(linejunk=None, charjunk=None)

Parámetros de palabra clave opcionales linejunk y charjunk son para funciones de filtrado (o None):

linejunk: Una función que acepta una sola cadena de texto como argumento y retorna verdadero si la cadena de texto es un elemento no deseado. Su valor por defecto es None, lo que significa que ninguna línea es considerada no deseada.

charjunk: Una función que acepta un solo carácter como argumento (una cadena de caracteres de longitud 1) y retorna verdadero si el carácter es un elemento no deseado. Su valor por defecto es None, lo que significa que ningún carácter es considerado no deseado.

Estas funciones de elementos no deseados aceleran la coincidencia para encontrar diferencies y no hacen que se ignoren líneas o caracteres diferentes. Lea la descripción del parámetro isjunk en el método find_longest_match() para una explicación mas detallada.

Los objetos Differ son usados (una vez generados los deltas) mediante un solo método:

compare(a, b)

Compara dos secuencias de líneas y genera el delta correspondiente (una secuencia de líneas).

Cada secuencia debe contener cadenas de texto individuales de una sola linea terminadas con una línea nueva. Este tipo de secuencias pueden ser obtenidas mediante el método readlines() de objetos de tipo archivo. Los delta generados consisten también en cadenas de texto terminadas en nuevas lineas, listas para imprimirse tal cual a través del método writelines() de un objeto de tipo archivo.

Ejemplo de Differ

Este ejemplo compara dos textos. Primero preparamos los textos, secuencias de cadenas de texto individuales de una sola línea terminadas con una línea nueva (este tipo de secuencias también pueden ser obtenidas mediante el método readlines() de objetos de tipo archivo):

>>> text1 = '''  1. Beautiful is better than ugly.
...   2. Explicit is better than implicit.
...   3. Simple is better than complex.
...   4. Complex is better than complicated.
... '''.splitlines(keepends=True)
>>> len(text1)
4
>>> text1[0][-1]
'\n'
>>> text2 = '''  1. Beautiful is better than ugly.
...   3.   Simple is better than complex.
...   4. Complicated is better than complex.
...   5. Flat is better than nested.
... '''.splitlines(keepends=True)

Luego instanciamos el objeto Differ:

>>> d = Differ()

Nótese que cuando instanciamos un objeto Differ deberíamos pasar funciones para filtrar lineas y caracteres no deseados. Consulte el constructor de Differ() para mas detalles.

Finalmente, comparamos las dos:

>>> result = list(d.compare(text1, text2))

result es una lista de cadenas de caracteres, entonces vamos a mostrarlo de una forma elegante:

>>> from pprint import pprint
>>> pprint(result)
['    1. Beautiful is better than ugly.\n',
 '-   2. Explicit is better than implicit.\n',
 '-   3. Simple is better than complex.\n',
 '+   3.   Simple is better than complex.\n',
 '?     ++\n',
 '-   4. Complex is better than complicated.\n',
 '?            ^                     ---- ^\n',
 '+   4. Complicated is better than complex.\n',
 '?           ++++ ^                      ^\n',
 '+   5. Flat is better than nested.\n']

Representado como una sola cadena de caracteres de múltiples líneas se ve así:

>>> import sys
>>> sys.stdout.writelines(result)
    1. Beautiful is better than ugly.
-   2. Explicit is better than implicit.
-   3. Simple is better than complex.
+   3.   Simple is better than complex.
?     ++
-   4. Complex is better than complicated.
?            ^                     ---- ^
+   4. Complicated is better than complex.
?           ++++ ^                      ^
+   5. Flat is better than nested.

Una interfaz de línea de comandos para difflib

Este ejemplo muestra como usar difflib para crear una herramienta de diferencias. También puedes encontrarla en la distribución estándar de Python como Tools/scripts/diff.py.

#!/usr/bin/env python3
""" Command line interface to difflib.py providing diffs in four formats:

* ndiff:    lists every line and highlights interline changes.
* context:  highlights clusters of changes in a before/after format.
* unified:  highlights clusters of changes in an inline format.
* html:     generates side by side comparison with change highlights.

"""

import sys, os, difflib, argparse
from datetime import datetime, timezone

def file_mtime(path):
    t = datetime.fromtimestamp(os.stat(path).st_mtime,
                               timezone.utc)
    return t.astimezone().isoformat()

def main():

    parser = argparse.ArgumentParser()
    parser.add_argument('-c', action='store_true', default=False,
                        help='Produce a context format diff (default)')
    parser.add_argument('-u', action='store_true', default=False,
                        help='Produce a unified format diff')
    parser.add_argument('-m', action='store_true', default=False,
                        help='Produce HTML side by side diff '
                             '(can use -c and -l in conjunction)')
    parser.add_argument('-n', action='store_true', default=False,
                        help='Produce a ndiff format diff')
    parser.add_argument('-l', '--lines', type=int, default=3,
                        help='Set number of context lines (default 3)')
    parser.add_argument('fromfile')
    parser.add_argument('tofile')
    options = parser.parse_args()

    n = options.lines
    fromfile = options.fromfile
    tofile = options.tofile

    fromdate = file_mtime(fromfile)
    todate = file_mtime(tofile)
    with open(fromfile) as ff:
        fromlines = ff.readlines()
    with open(tofile) as tf:
        tolines = tf.readlines()

    if options.u:
        diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
    elif options.n:
        diff = difflib.ndiff(fromlines, tolines)
    elif options.m:
        diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n)
    else:
        diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)

    sys.stdout.writelines(diff)

if __name__ == '__main__':
    main()