Python difflib: destacando las diferencias en línea?

Al comparar líneas similares, quiero resaltar las diferencias en la misma línea:

a) lorem ipsum dolor sit amet b) lorem foo ipsum dolor amet lorem foo ipsum dolor sit amet 

Si bien difflib.HtmlDiff parece hacer este tipo de resaltado en línea, produce un marcado muy detallado.

Desafortunadamente, no he podido encontrar otra clase / método que no funcione línea por línea.

¿Me estoy perdiendo algo? ¡Cualquier indicador sería apreciada!

Para su ejemplo simple:

 import difflib def show_diff(seqm): """Unify operations between two compared strings seqm is a difflib.SequenceMatcher instance whose a & b are strings""" output= [] for opcode, a0, a1, b0, b1 in seqm.get_opcodes(): if opcode == 'equal': output.append(seqm.a[a0:a1]) elif opcode == 'insert': output.append("" + seqm.b[b0:b1] + "") elif opcode == 'delete': output.append("" + seqm.a[a0:a1] + "") elif opcode == 'replace': raise NotImplementedError, "what to do with 'replace' opcode?" else: raise RuntimeError, "unexpected opcode" return ''.join(output) >>> sm= difflib.SequenceMatcher(None, "lorem ipsum dolor sit amet", "lorem foo ipsum dolor amet") >>> show_diff(sm) 'lorem foo ipsum dolor sit amet' 

Esto funciona con cuerdas. Debería decidir qué hacer con los códigos de operación “reemplazar”.

difflib.SequenceMatcher operará en líneas simples. Puede usar los “códigos de operación” para determinar cómo cambiar la primera línea para convertirla en la segunda línea.

Aquí hay una diferencia en línea inspirada en la respuesta de @tzot anterior (también compatible con Python 3)

 def inline_diff(a, b): import difflib matcher = difflib.SequenceMatcher(None, a, b) def process_tag(tag, i1, i2, j1, j2): if tag == 'replace': return '{' + matcher.a[i1:i2] + ' -> ' + matcher.b[j1:j2] + '}' if tag == 'delete': return '{- ' + matcher.a[i1:i2] + '}' if tag == 'equal': return matcher.a[i1:i2] if tag == 'insert': return '{+ ' + matcher.b[j1:j2] + '}' assert false, "Unknown tag %r"%tag return ''.join(process_tag(*t) for t in matcher.get_opcodes()) 

No es perfecto; por ejemplo, sería bueno expandir los códigos de operación “reemplazar” para reconocer la palabra completa reemplazada en lugar de solo unas pocas letras diferentes, pero es un buen lugar para comenzar.

Salida de muestra:

 >>> a='Lorem ipsum dolor sit amet consectetur adipiscing' >>> b='Lorem bananas ipsum cabbage sit amet adipiscing' >>> print(inline_diff(a, b)) Lorem{+ bananas} ipsum {dolor -> cabbage} sit amet{- consectetur} adipiscing