difflib — Hilfsprogramme zur Berechnung von Differenzen

Quellcode: Lib/difflib.py


Dieses Modul stellt Klassen und Funktionen zum Vergleichen von Sequenzen bereit. Es kann beispielsweise zum Vergleichen von Dateien verwendet werden und kann Informationen über Dateidifferenzen in verschiedenen Formaten liefern, einschließlich HTML und Kontext- und Unified-Diffs. Zum Vergleichen von Verzeichnissen und Dateien siehe auch das Modul filecmp.

class difflib.SequenceMatcher

Dies ist eine flexible Klasse zum Vergleichen von Paaren von Sequenzen beliebigen Typs, solange die Sequenzelemente hashbar sind. Der grundlegende Algorithmus ist älter als ein Algorithmus, der Ende der 1980er Jahre von Ratcliff und Obershelp unter dem hyperbolischen Namen „gestalt pattern matching“ veröffentlicht wurde, und ein wenig ausgefeilter. Die Idee ist, die längste zusammenhängende übereinstimmende Untersequenz zu finden, die keine „Junk“-Elemente enthält; diese „Junk“-Elemente sind solche, die in gewisser Weise uninteressant sind, wie z. B. Leerzeilen oder Leerzeichen. (Die Behandlung von Junk ist eine Erweiterung des Ratcliff- und Obershelp-Algorithmus.) Dieselbe Idee wird dann rekursiv auf die Teile der Sequenzen links und rechts der übereinstimmenden Untersequenz angewendet. Dies liefert keine minimalen Editiersequenzen, führt aber tendenziell zu Übereinstimmungen, die für Menschen „richtig aussehen“.

Timing: Der grundlegende Ratcliff-Obershelp-Algorithmus hat im Worst Case eine kubische Zeitkomplexität und im erwarteten Fall eine quadratische Zeitkomplexität. SequenceMatcher hat im Worst Case eine quadratische Zeitkomplexität und ein erwartetes Verhalten, das auf komplizierte Weise davon abhängt, wie viele Elemente die Sequenzen gemeinsam haben; die Best-Case-Zeit ist linear.

Automatische Junk-Heuristik: SequenceMatcher unterstützt eine Heuristik, die bestimmte Sequenzelemente automatisch als Junk behandelt. Die Heuristik zählt, wie oft jedes einzelne Element in der Sequenz vorkommt. Wenn Duplikate eines Elements (nach dem ersten) mehr als 1 % der Sequenz ausmachen und die Sequenz mindestens 200 Elemente lang ist, wird dieses Element als „populär“ markiert und für den Sequenzvergleich als Junk behandelt. Diese Heuristik kann deaktiviert werden, indem das Argument autojunk beim Erstellen des SequenceMatcher auf False gesetzt wird.

Geändert in Version 3.2: Parameter autojunk hinzugefügt.

class difflib.Differ

Dies ist eine Klasse zum Vergleichen von Textzeilensequenzen und zur Erzeugung von menschenlesbaren Differenzen oder Deltas. Differ verwendet SequenceMatcher sowohl zum Vergleichen von Zeilensequenzen als auch zum Vergleichen von Zeichensequenzen innerhalb ähnlicher (nahezu übereinstimmender) Zeilen.

Jede Zeile eines Differ-Deltas beginnt mit einem zweibuchstabigen Code

Code

Bedeutung

'- '

nur in Sequenz 1 vorhandene Zeile

'+ '

nur in Sequenz 2 vorhandene Zeile

'  '

gemeinsame Zeile beider Sequenzen

'? '

in keiner der Eingabesequenzen vorhandene Zeile

Zeilen, die mit „?“ beginnen, versuchen, das Auge auf Zeilenunterschiede zu lenken und waren in keiner der Eingabesequenzen vorhanden. Diese Zeilen können verwirrend sein, wenn die Sequenzen Leerzeichen wie Leerzeichen, Tabs oder Zeilenumbrüche enthalten.

class difflib.HtmlDiff

Diese Klasse kann verwendet werden, um eine HTML-Tabelle (oder eine vollständige HTML-Datei, die die Tabelle enthält) zu erstellen, die einen nebeneinander liegenden, zeilenweisen Vergleich von Text mit Hervorhebung von Zeilen- und Zeilenänderungen anzeigt. Die Tabelle kann entweder im vollständigen oder im kontextuellen Differenzmodus generiert werden.

Der Konstruktor für diese Klasse ist

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

Initialisiert eine Instanz von HtmlDiff.

tabsize ist ein optionales Schlüsselwortargument zur Angabe des Tabulatorabstands und hat den Standardwert 8.

wrapcolumn ist ein optionales Schlüsselwort zur Angabe der Spaltennummer, bei der Zeilen umgebrochen und umgebrochen werden; der Standardwert ist None, was bedeutet, dass Zeilen nicht umgebrochen werden.

linejunk und charjunk sind optionale Schlüsselwortargumente, die an ndiff() übergeben werden (von HtmlDiff verwendet, um die nebeneinander liegenden HTML-Differenzen zu generieren). Siehe die Dokumentation zu ndiff() für Standardwerte und Beschreibungen der Argumente.

Die folgenden Methoden sind öffentlich

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

Vergleicht fromlines und tolines (Listen von Zeichenketten) und gibt eine Zeichenkette zurück, die eine vollständige HTML-Datei mit einer Tabelle ist, die zeilenweise Unterschiede mit hervorgehobenen Zeilen- und Zeilenänderungen zeigt.

fromdesc und todesc sind optionale Schlüsselwortargumente zur Angabe von Spaltenüberschriften für die von-/zu-Dateien (beide sind standardmäßig leer).

context und numlines sind beides optionale Schlüsselwortargumente. Setzen Sie context auf True, wenn kontextuelle Unterschiede angezeigt werden sollen, andernfalls ist der Standardwert False, um die vollständigen Dateien anzuzeigen. numlines hat den Standardwert 5. Wenn context True ist, steuert numlines die Anzahl der Kontextzeilen, die die Differenzhervorhebungen umgeben. Wenn context False ist, steuert numlines die Anzahl der Zeilen, die vor einer Differenzhervorhebung angezeigt werden, wenn die „nächsten“-Hyperlinks verwendet werden (ein Wert von Null würde dazu führen, dass die „nächsten“-Hyperlinks die nächste Differenzhervorhebung am oberen Rand des Browsers ohne führenden Kontext platzieren).

Hinweis

fromdesc und todesc werden als unescaped HTML interpretiert und sollten ordnungsgemäß escaped werden, wenn Eingaben von nicht vertrauenswürdigen Quellen empfangen werden.

Geändert in Version 3.5: Das Schlüsselwortargument charset wurde hinzugefügt. Der Standard-Charset des HTML-Dokuments änderte sich von 'ISO-8859-1' zu 'utf-8'.

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

Vergleicht fromlines und tolines (Listen von Zeichenketten) und gibt eine Zeichenkette zurück, die eine vollständige HTML-Tabelle ist, die zeilenweise Unterschiede mit hervorgehobenen Zeilen- und Zeilenänderungen zeigt.

Die Argumente für diese Methode sind die gleichen wie für die Methode make_file().

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

Vergleicht a und b (Listen von Zeichenketten); gibt ein Delta zurück (ein Generator, der die Deltazeilen generiert) im Kontext-Diff-Format.

Kontext-Diffs sind eine kompakte Möglichkeit, nur die geänderten Zeilen plus einige Kontextzeilen anzuzeigen. Die Änderungen werden im Vorher/Nachher-Stil dargestellt. Die Anzahl der Kontextzeilen wird durch n bestimmt, das standardmäßig drei ist.

Standardmäßig werden die Diff-Steuerzeilen (die mit *** oder ---) mit einem nachfolgenden Zeilenumbruch erstellt. Dies ist hilfreich, damit Eingaben, die aus io.IOBase.readlines() erstellt wurden, zu Diffs führen, die für die Verwendung mit io.IOBase.writelines() geeignet sind, da sowohl die Eingaben als auch die Ausgaben nachfolgende Zeilenumbrüche haben.

Für Eingaben, die keine nachfolgenden Zeilenumbrüche haben, setzen Sie das Argument lineterm auf "", damit die Ausgabe einheitlich zeilenumbruchfrei ist.

Das Kontext-Diff-Format enthält normalerweise eine Kopfzeile für Dateinamen und Änderungszeiten. Beliebige oder alle dieser können mit Zeichenketten für fromfile, tofile, fromfiledate und tofiledate angegeben werden. Die Änderungszeiten werden normalerweise im ISO 8601-Format ausgedrückt. Wenn sie nicht angegeben werden, sind die Zeichenketten standardmäßig leer.

>>> import sys
>>> from difflib import *
>>> 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

Siehe Eine Befehlszeilenschnittstelle für difflib für ein detaillierteres Beispiel.

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

Gibt eine Liste der besten „gut genug“ Übereinstimmungen zurück. word ist eine Sequenz, für die nahe Übereinstimmungen gesucht werden (typischerweise eine Zeichenkette), und possibilities ist eine Liste von Sequenzen, mit denen word abgeglichen werden soll (typischerweise eine Liste von Zeichenketten).

Das optionale Argument n (Standard 3) ist die maximale Anzahl von nahen Übereinstimmungen, die zurückgegeben werden sollen; n muss größer als 0 sein.

Das optionale Argument cutoff (Standard 0.6) ist eine Gleitkommazahl im Bereich [0, 1]. Möglichkeiten, die nicht mindestens diesen Grad an Ähnlichkeit mit word aufweisen, werden ignoriert.

Die besten (höchstens n) Übereinstimmungen unter den Möglichkeiten werden in einer Liste zurückgegeben, sortiert nach Ähnlichkeitsbewertung, am ähnlichsten zuerst.

>>> 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)

Vergleicht a und b (Listen von Zeichenketten); gibt ein Delta im Differ-Stil zurück (ein Generator, der die Deltazeilen generiert).

Optionale Schlüsselwortparameter linejunk und charjunk sind Filterfunktionen (oder None)

linejunk: Eine Funktion, die ein einzelnes Zeichenkettenargument akzeptiert und True zurückgibt, wenn die Zeile Junk ist, oder False, wenn nicht. Der Standardwert ist None. Es gibt auch eine Modul-Funktion IS_LINE_JUNK(), die Zeilen ohne sichtbare Zeichen filtert, mit Ausnahme von höchstens einem Hash-Zeichen ('#') – die zugrunde liegende Klasse SequenceMatcher führt jedoch eine dynamische Analyse durch, welche Zeilen so häufig sind, dass sie Rauschen darstellen, und dies funktioniert normalerweise besser als die Verwendung dieser Funktion.

charjunk: Eine Funktion, die ein Zeichen (eine Zeichenkette der Länge 1) akzeptiert und zurückgibt, ob das Zeichen Junk ist oder nicht. Der Standardwert ist die Modul-Funktion IS_CHARACTER_JUNK(), die Leerzeichen filtert (ein Leerzeichen oder Tabulator; es ist keine gute Idee, hier einen Zeilenumbruch einzubeziehen!).

>>> 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)

Gibt eine der beiden Sequenzen zurück, die ein Delta erzeugt haben.

Extrahieren Sie aus einer von Differ.compare() oder ndiff() erzeugten sequence Zeilen, die aus Datei 1 oder 2 stammen (Parameter which), und entfernen Sie die Zeilenpräfixe.

Beispiel

>>> 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')

Vergleicht a und b (Listen von Zeichenketten); gibt ein Delta zurück (ein Generator, der die Deltazeilen generiert) im Unified-Diff-Format.

Unified-Diffs sind eine kompakte Möglichkeit, nur die geänderten Zeilen plus einige Kontextzeilen anzuzeigen. Die Änderungen werden im Inline-Stil dargestellt (anstelle von separaten Vorher/Nachher-Blöcken). Die Anzahl der Kontextzeilen wird durch n bestimmt, das standardmäßig drei ist.

Standardmäßig werden die Diff-Steuerzeilen (die mit ---, +++ oder @@) mit einem nachfolgenden Zeilenumbruch erstellt. Dies ist hilfreich, damit Eingaben, die aus io.IOBase.readlines() erstellt wurden, zu Diffs führen, die für die Verwendung mit io.IOBase.writelines() geeignet sind, da sowohl die Eingaben als auch die Ausgaben nachfolgende Zeilenumbrüche haben.

Für Eingaben, die keine nachfolgenden Zeilenumbrüche haben, setzen Sie das Argument lineterm auf "", damit die Ausgabe einheitlich zeilenumbruchfrei ist.

Das Unified-Diff-Format enthält normalerweise eine Kopfzeile für Dateinamen und Änderungszeiten. Beliebige oder alle dieser können mit Zeichenketten für fromfile, tofile, fromfiledate und tofiledate angegeben werden. Die Änderungszeiten werden normalerweise im ISO 8601-Format ausgedrückt. Wenn sie nicht angegeben werden, sind die Zeichenketten standardmäßig leer.

>>> 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

Siehe Eine Befehlszeilenschnittstelle für difflib für ein detaillierteres Beispiel.

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

Vergleicht a und b (Listen von Bytes-Objekten) mit dfunc; liefert eine Sequenz von Deltazeilen (ebenfalls Bytes) im von dfunc zurückgegebenen Format. dfunc muss aufrufbar sein, typischerweise entweder unified_diff() oder context_diff().

Ermöglicht den Vergleich von Daten mit unbekannter oder inkonsistenter Kodierung. Alle Eingaben außer n müssen Bytes-Objekte und keine str sein. Funktioniert durch verlustfreies Konvertieren aller Eingaben (außer n) in str und Aufrufen von dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm). Die Ausgabe von dfunc wird dann zurück in Bytes konvertiert, sodass die Deltazeilen, die Sie erhalten, die gleichen unbekannten/inkonsistenten Kodierungen wie a und b haben.

Hinzugefügt in Version 3.5.

difflib.IS_LINE_JUNK(line)

Gibt True für ignorierbare Zeilen zurück. Die Zeile line ist ignorable, wenn line leer ist oder ein einzelnes '#' enthält, andernfalls ist sie nicht ignorable. Wird als Standard für den Parameter linejunk in ndiff() in älteren Versionen verwendet.

difflib.IS_CHARACTER_JUNK(ch)

Gibt True für ignorierbare Zeichen zurück. Das Zeichen ch ist ignorable, wenn ch ein Leerzeichen oder Tabulator ist, andernfalls ist es nicht ignorable. Wird als Standard für den Parameter charjunk in ndiff() verwendet.

Siehe auch

Mustererkennung: Der Gestalt-Ansatz

Diskussion eines ähnlichen Algorithmus von John W. Ratcliff und D. E. Metzener. Dies wurde im Dr. Dobb’s Journal im Juli 1988 veröffentlicht.

SequenceMatcher-Objekte

Die Klasse SequenceMatcher hat diesen Konstruktor

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

Das optionale Argument isjunk muss None (der Standardwert) oder eine einargumentige Funktion sein, die ein Sequenzelement nimmt und True zurückgibt, wenn und nur wenn das Element „Junk“ ist und ignoriert werden sollte. Die Übergabe von None für isjunk ist äquivalent zur Übergabe von lambda x: False; anders ausgedrückt, keine Elemente werden ignoriert. Zum Beispiel: Übergabe

lambda x: x in " \t"

wenn Sie Zeilen als Zeichenketten vergleichen und sich nicht an Leerzeichen oder harten Tabs ausrichten möchten.

Die optionalen Argumente a und b sind die zu vergleichenden Sequenzen; beide sind standardmäßig leere Zeichenketten. Die Elemente beider Sequenzen müssen hashbar sein.

Das optionale Argument autojunk kann verwendet werden, um die automatische Junk-Heuristik zu deaktivieren.

Geändert in Version 3.2: Parameter autojunk hinzugefügt.

SequenceMatcher-Objekte erhalten drei Datenattribute: bjunk ist die Menge der Elemente von b, für die isjunk True ist; bpopular ist die Menge der Nicht-Junk-Elemente, die von der Heuristik als beliebt erachtet werden (wenn sie nicht deaktiviert ist); b2j ist ein Dictionary, das die verbleibenden Elemente von b auf eine Liste der Positionen abbildet, an denen sie vorkommen. Alle drei werden zurückgesetzt, wann immer b mit set_seqs() oder set_seq2() zurückgesetzt wird.

Hinzugefügt in Version 3.2: Die Attribute bjunk und bpopular.

SequenceMatcher-Objekte haben die folgenden Methoden

set_seqs(a, b)

Setzt die beiden zu vergleichenden Sequenzen.

SequenceMatcher berechnet und speichert detaillierte Informationen über die zweite Sequenz. Wenn Sie also eine Sequenz mit vielen Sequenzen vergleichen möchten, verwenden Sie set_seq2(), um die häufig verwendete Sequenz einmal festzulegen, und rufen Sie set_seq1() wiederholt auf, einmal für jede der anderen Sequenzen.

set_seq1(a)

Setzt die erste zu vergleichende Sequenz. Die zweite zu vergleichende Sequenz wird nicht geändert.

set_seq2(b)

Setzt die zweite zu vergleichende Sequenz. Die erste zu vergleichende Sequenz wird nicht geändert.

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

Findet den längsten übereinstimmenden Block in a[alo:ahi] und b[blo:bhi].

Wenn isjunk weggelassen oder None war, gibt find_longest_match() (i, j, k) zurück, so dass a[i:i+k] gleich b[j:j+k] ist, wobei alo <= i <= i+k <= ahi und blo <= j <= j+k <= bhi. Für alle (i', j', k'), die diese Bedingungen erfüllen, gelten auch die zusätzlichen Bedingungen k >= k', i <= i' und wenn i == i', j <= j'. Anders ausgedrückt: Von allen maximalen übereinstimmenden Blöcken wird einer zurückgegeben, der in a am frühesten beginnt, und von allen maximalen übereinstimmenden Blöcken, die in a am frühesten beginnen, wird derjenige zurückgegeben, der in b am frühesten beginnt.

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

Wenn isjunk bereitgestellt wurde, wird zunächst der längste übereinstimmende Block wie oben ermittelt, jedoch mit der zusätzlichen Einschränkung, dass kein Junk-Element im Block vorkommt. Dann wird dieser Block so weit wie möglich erweitert, indem (nur) Junk-Elemente auf beiden Seiten übereinstimmen. Der resultierende Block stimmt also nie mit Junk überein, außer wenn identischer Junk zufällig neben einer interessanten Übereinstimmung liegt.

Hier ist dasselbe Beispiel wie zuvor, aber unter Berücksichtigung von Leerzeichen als Junk. Dies verhindert, dass ' abcd' direkt mit dem ' abcd' am Ende der zweiten Sequenz übereinstimmt. Stattdessen kann nur 'abcd' übereinstimmen und mit dem am weitesten links stehenden 'abcd' in der zweiten Sequenz übereinstimmen

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

Wenn keine Blöcke übereinstimmen, gibt dies (alo, blo, 0) zurück.

Diese Methode gibt ein benanntes Tupel Match(a, b, size) zurück.

Geändert in Version 3.9: Standardargumente hinzugefügt.

get_matching_blocks()

Gibt eine Liste von Tripeln zurück, die nicht überlappende übereinstimmende Teilsequenzen beschreiben. Jedes Tripel hat die Form (i, j, n) und bedeutet, dass a[i:i+n] == b[j:j+n]. Die Tripel sind monoton steigend in i und j.

Das letzte Tripel ist ein Dummy und hat den Wert (len(a), len(b), 0). Es ist das einzige Tripel mit n == 0. Wenn (i, j, n) und (i', j', n') aufeinanderfolgende Tripel in der Liste sind und das zweite nicht das letzte Tripel in der Liste ist, dann gilt i+n < i' oder j+n < j'; mit anderen Worten, aufeinanderfolgende Tripel beschreiben immer nicht aufeinanderfolgende gleiche Blöcke.

>>> 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()

Gibt eine Liste von 5-Tupeln zurück, die beschreiben, wie a in b umgewandelt wird. Jedes Tupel hat die Form (tag, i1, i2, j1, j2). Das erste Tupel hat i1 == j1 == 0, und die verbleibenden Tupel haben i1 gleich dem i2 des vorhergehenden Tupels und, ebenso, j1 gleich dem vorherigen j2.

Die tag-Werte sind Zeichenketten mit diesen Bedeutungen:

Wert

Bedeutung

'replace'

a[i1:i2] sollte durch b[j1:j2] ersetzt werden.

'delete'

a[i1:i2] sollte gelöscht werden. Beachten Sie, dass in diesem Fall j1 == j2 gilt.

'insert'

b[j1:j2] sollte an a[i1:i1] eingefügt werden. Beachten Sie, dass in diesem Fall i1 == i2 gilt.

'equal'

a[i1:i2] == b[j1:j2] (die Teilsequenzen sind gleich).

Zum Beispiel

>>> 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)

Gibt einen Generator von Gruppen mit bis zu n Zeilen Kontext zurück.

Ausgehend von den von get_opcodes() zurückgegebenen Gruppen teilt diese Methode kleinere Änderungscluster auf und eliminiert dazwischen liegende Bereiche, die keine Änderungen aufweisen.

Die Gruppen werden im gleichen Format wie von get_opcodes() zurückgegeben.

ratio()

Gibt ein Maß für die Ähnlichkeit der Sequenzen als Gleitkommazahl im Bereich [0, 1] zurück.

Wobei T die Gesamtzahl der Elemente in beiden Sequenzen ist und M die Anzahl der Übereinstimmungen ist, ist dies 2.0*M / T. Beachten Sie, dass dies 1.0 ist, wenn die Sequenzen identisch sind, und 0.0, wenn sie nichts gemeinsam haben.

Die Berechnung ist teuer, wenn get_matching_blocks() oder get_opcodes() noch nicht aufgerufen wurde. In diesem Fall sollten Sie vielleicht zuerst quick_ratio() oder real_quick_ratio() versuchen, um eine Obergrenze zu erhalten.

Hinweis

Vorsicht: Das Ergebnis eines Aufrufs von ratio() kann von der Reihenfolge der Argumente abhängen. Zum Beispiel

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

Gibt relativ schnell eine Obergrenze für ratio() zurück.

real_quick_ratio()

Gibt sehr schnell eine Obergrenze für ratio() zurück.

Die drei Methoden, die das Verhältnis von übereinstimmenden zu gesamten Zeichen zurückgeben, können aufgrund unterschiedlicher Approximationsgrade unterschiedliche Ergebnisse liefern, obwohl quick_ratio() und real_quick_ratio() immer mindestens so groß wie ratio() sind.

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

Beispiele für SequenceMatcher

Dieses Beispiel vergleicht zwei Zeichenketten und betrachtet Leerzeichen als "unerwünscht".

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

ratio() gibt eine Gleitkommazahl im Bereich [0, 1] zurück, die die Ähnlichkeit der Sequenzen misst. Als Faustregel gilt, dass ein ratio()-Wert über 0,6 bedeutet, dass die Sequenzen nahe beieinander liegen.

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

Wenn Sie nur daran interessiert sind, wo die Sequenzen übereinstimmen, ist get_matching_blocks() praktisch.

>>> 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

Beachten Sie, dass das letzte von get_matching_blocks() zurückgegebene Tupel immer ein Dummy ist, (len(a), len(b), 0), und dies ist der einzige Fall, in dem das letzte Tupel-Element (Anzahl der übereinstimmenden Elemente) 0 ist.

Wenn Sie wissen möchten, wie Sie die erste Sequenz in die zweite ändern können, verwenden Sie 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]

Siehe auch

Differ-Objekte

Beachten Sie, dass von Differ generierte Deltas keinen Anspruch auf **minimale** Diffs erheben. Im Gegenteil, minimale Diffs sind oft kontraintuitiv, da sie überall synchronisieren, wo es möglich ist, manchmal zufällige Übereinstimmungen über 100 Seiten hinweg. Die Beschränkung von Synchronisationspunkten auf zusammenhängende Übereinstimmungen bewahrt eine gewisse Lokalität, auf Kosten der Produktion eines längeren Diffs.

Die Klasse Differ hat diesen Konstruktor

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

Optionale Schlüsselwortparameter linejunk und charjunk sind für Filterfunktionen (oder None).

linejunk: Eine Funktion, die ein einzelnes Zeichenkettenargument akzeptiert und wahr zurückgibt, wenn die Zeichenkette unerwünscht ist. Der Standardwert ist None, was bedeutet, dass keine Zeile als unerwünscht betrachtet wird.

charjunk: Eine Funktion, die ein einzelnes Zeichen als Argument akzeptiert (eine Zeichenkette der Länge 1) und wahr zurückgibt, wenn das Zeichen unerwünscht ist. Der Standardwert ist None, was bedeutet, dass kein Zeichen als unerwünscht betrachtet wird.

Diese Junk-Filterfunktionen beschleunigen die Suche nach Unterschieden und führen nicht dazu, dass abweichende Zeilen oder Zeichen ignoriert werden. Lesen Sie die Beschreibung des isjunk-Parameters der Methode find_longest_match() für eine Erklärung.

Differ-Objekte werden (Deltas werden generiert) über eine einzige Methode verwendet:

compare(a, b)

Vergleicht zwei Sequenzen von Zeilen und generiert das Delta (eine Sequenz von Zeilen).

Jede Sequenz muss einzelne Zeilenstrings enthalten, die mit Zeilenumbrüchen enden. Solche Sequenzen können aus der `readlines()`-Methode von dateiähnlichen Objekten erhalten werden. Das generierte Delta besteht ebenfalls aus zeilenumbruchterminierten Strings, die bereit sind, als solche über die `writelines()`-Methode eines dateiähnlichen Objekts gedruckt zu werden.

Beispiel für Differ

Dieses Beispiel vergleicht zwei Texte. Zuerst richten wir die Texte ein, Sequenzen von einzelnen Zeilenstrings, die mit Zeilenumbrüchen enden (solche Sequenzen können auch aus der `readlines()`-Methode von dateiähnlichen Objekten erhalten werden).

>>> 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)

Als Nächstes instanziieren wir ein Differ-Objekt.

>>> d = Differ()

Beachten Sie, dass wir beim Instanziieren eines Differ-Objekts Funktionen übergeben können, um Zeilen- und Zeichen-"Junk" herauszufiltern. Einzelheiten finden Sie im Konstruktor Differ().

Schließlich vergleichen wir die beiden.

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

result ist eine Liste von Strings, also formatieren wir sie zur besseren Lesbarkeit.

>>> 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']

Als einzelner mehrzeiliger String sieht es so aus:

>>> 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.

Eine Befehlszeilenschnittstelle zu difflib

Dieses Beispiel zeigt, wie difflib verwendet werden kann, um ein Dienstprogramm im Stil von diff zu erstellen.

""" 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()

Beispiel für ndiff

Dieses Beispiel zeigt die Verwendung von difflib.ndiff().

"""ndiff [-q] file1 file2
    or
ndiff (-r1 | -r2) < ndiff_output > file1_or_file2

Print a human-friendly file difference report to stdout.  Both inter-
and intra-line differences are noted.  In the second form, recreate file1
(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.

In the first form, if -q ("quiet") is not specified, the first two lines
of output are

-: file1
+: file2

Each remaining line begins with a two-letter code:

    "- "    line unique to file1
    "+ "    line unique to file2
    "  "    line common to both files
    "? "    line not present in either input file

Lines beginning with "? " attempt to guide the eye to intraline
differences, and were not present in either input file.  These lines can be
confusing if the source files contain tab characters.

The first file can be recovered by retaining only lines that begin with
"  " or "- ", and deleting those 2-character prefixes; use ndiff with -r1.

The second file can be recovered similarly, but by retaining only "  " and
"+ " lines; use ndiff with -r2; or, on Unix, the second file can be
recovered by piping the output through

    sed -n '/^[+ ] /s/^..//p'
"""

__version__ = 1, 7, 0

import difflib, sys

def fail(msg):
    out = sys.stderr.write
    out(msg + "\n\n")
    out(__doc__)
    return 0

# open a file & return the file object; gripe and return 0 if it
# couldn't be opened
def fopen(fname):
    try:
        return open(fname)
    except IOError as detail:
        return fail("couldn't open " + fname + ": " + str(detail))

# open two files & spray the diff to stdout; return false iff a problem
def fcompare(f1name, f2name):
    f1 = fopen(f1name)
    f2 = fopen(f2name)
    if not f1 or not f2:
        return 0

    a = f1.readlines(); f1.close()
    b = f2.readlines(); f2.close()
    for line in difflib.ndiff(a, b):
        print(line, end=' ')

    return 1

# crack args (sys.argv[1:] is normal) & compare;
# return false iff a problem

def main(args):
    import getopt
    try:
        opts, args = getopt.getopt(args, "qr:")
    except getopt.error as detail:
        return fail(str(detail))
    noisy = 1
    qseen = rseen = 0
    for opt, val in opts:
        if opt == "-q":
            qseen = 1
            noisy = 0
        elif opt == "-r":
            rseen = 1
            whichfile = val
    if qseen and rseen:
        return fail("can't specify both -q and -r")
    if rseen:
        if args:
            return fail("no args allowed with -r option")
        if whichfile in ("1", "2"):
            restore(whichfile)
            return 1
        return fail("-r value must be 1 or 2")
    if len(args) != 2:
        return fail("need 2 filename args")
    f1name, f2name = args
    if noisy:
        print('-:', f1name)
        print('+:', f2name)
    return fcompare(f1name, f2name)

# read ndiff output from stdin, and print file1 (which=='1') or
# file2 (which=='2') to stdout

def restore(which):
    restored = difflib.restore(sys.stdin.readlines(), which)
    sys.stdout.writelines(restored)

if __name__ == '__main__':
    main(sys.argv[1:])