difflib — Hjälpmedel för att beräkna deltan

Källkod: Lib/difflib.py


Den här modulen innehåller klasser och funktioner för jämförelse av sekvenser. Den kan till exempel användas för att jämföra filer och kan producera information om filskillnader i olika format, inklusive HTML och context och unified diffs. För jämförelse av kataloger och filer, se även modulen filecmp.

class difflib.SequenceMatcher

Detta är en flexibel klass för att jämföra par av sekvenser av alla typer, så länge som sekvenselementen är hashable. Den grundläggande algoritmen föregår, och är lite mer avancerad än, en algoritm som publicerades i slutet av 1980-talet av Ratcliff och Obershelp under det hyperboliska namnet ”gestalt pattern matching” Tanken är att hitta den längsta sammanhängande matchande undersekvensen som inte innehåller några ”skräp”-element; dessa ”skräp”-element är sådana som är ointressanta i någon mening, till exempel tomma rader eller blanksteg. (Hantering av skräp är en utvidgning av Ratcliff och Obershelp-algoritmen.) Samma idé tillämpas sedan rekursivt på delarna av sekvenserna till vänster och till höger om den matchande undersekvensen. Detta ger inte minimala redigeringssekvenser, men tenderar att ge matchningar som ”ser rätt ut” för människor.

Timing: Den grundläggande Ratcliff-Obershelp-algoritmen är kubisk tid i värsta fall och kvadratisk tid i det förväntade fallet. SequenceMatcher är kvadratisk tid i värsta fall och har ett förväntat beteende som på ett komplicerat sätt beror på hur många element sekvenserna har gemensamt; bästa fall är tiden linjär.

Automatisk skräpheuristik: SequenceMatcher stöder en heuristik som automatiskt behandlar vissa sekvensobjekt som skräp. Heuristiken räknar hur många gånger varje enskilt objekt förekommer i sekvensen. Om ett objekts duplikat (efter det första) utgör mer än 1% of av sekvensen och sekvensen är minst 200 objekt lång, markeras detta objekt som ”populärt” och behandlas som skräp vid sekvensmatchning. Denna heuristik kan stängas av genom att ställa in argumentet autojunk till False när du skapar SequenceMatcher.

Ändrad i version 3.2: Lagt till parametern autojunk.

class difflib.Differ

Detta är en klass för att jämföra sekvenser av textrader och producera mänskligt läsbara skillnader eller deltan. Differ använder SequenceMatcher både för att jämföra sekvenser av rader och för att jämföra sekvenser av tecken inom liknande (nästan matchande) rader.

Varje rad i ett Differ delta börjar med en kod på två bokstäver:

Kod

Betydelse

'- '

linje unik för sekvens 1

'+ '

linje unik för sekvens 2

' '

linje som är gemensam för båda sekvenserna

'? '

rad som inte finns i någon av de ingående sekvenserna

Rader som börjar med ”?” försöker vägleda ögat till intralinjeskillnader och fanns inte i någon av indatasekvenserna. Dessa rader kan vara förvirrande om sekvenserna innehåller tecken för blanksteg, t.ex. mellanslag, tabbar eller radbrytningar.

class difflib.HtmlDiff

Denna klass kan användas för att skapa en HTML-tabell (eller en komplett HTML-fil som innehåller tabellen) som visar en jämförelse av text sida vid sida, rad för rad med markeringar för ändringar mellan och inom raderna. Tabellen kan genereras i antingen fullständigt eller kontextuellt skillnadsläge.

Konstruktören för denna klass är:

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

Initialiserar en instans av HtmlDiff.

tabsize är ett valfritt nyckelordsargument för att ange tabbstoppsavståndet och standardvärdet är 8.

wrapcolumn är ett valfritt nyckelord för att ange kolumnnumret där raderna bryts och omsluts, standard är None där raderna inte omsluts.

linejunk och charjunk är valfria nyckelordsargument som skickas till ndiff() (används av HtmlDiff för att generera HTML-skillnader sida vid sida). Se ndiff() dokumentation för argumentens standardvärden och beskrivningar.

Följande metoder är publika:

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

Jämför fromlines och tolines (listor med strängar) och returnerar en sträng som är en komplett HTML-fil innehållande en tabell som visar skillnader rad för rad med förändringar mellan och inom raderna markerade.

fromdesc och todesc är valfria nyckelordsargument för att ange kolumnhuvudsträngar för från/till-filen (båda har en tom sträng som standard).

context och numlines är båda valfria nyckelordsargument. Sätt context till True när kontextuella skillnader ska visas, annars är standardvärdet False för att visa de fullständiga filerna. numlines är som standard 5. När context är True styr numlines antalet kontextlinjer som omger de markerade skillnaderna. När kontext är False styr numlines antalet rader som visas före en skillnadsmarkering när du använder hyperlänkarna ”next” (om du ställer in noll skulle hyperlänkarna ”next” placera nästa skillnadsmarkering högst upp i webbläsaren utan någon ledande kontext).

Anteckning

fromdesc och todesc tolkas som oescapad HTML och bör escapas på rätt sätt när man tar emot indata från icke tillförlitliga källor.

Ändrad i version 3.5: charset nyckelord-bara argument lades till. HTML-dokumentets standardcharset ändrades från 'ISO-8859-1' till 'utf-8'.

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

Jämför fromlines och tolines (listor med strängar) och returnerar en sträng som är en komplett HTML-tabell som visar skillnader rad för rad med förändringar mellan och inom raderna markerade.

Argumenten för den här metoden är desamma som för metoden make_file().

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

Jämför a och b (listor med strängar); returnerar ett delta (en generator som genererar delta-raderna) i context diff-format.

Kontextdifferenser är ett kompakt sätt att visa bara de rader som har ändrats plus några rader med sammanhang. Ändringarna visas i en före/efter-stil. Antalet kontextrader anges av n som standard är tre.

Som standard skapas kontrollinjerna för diff (de med *** eller ---) med en efterföljande ny rad. Detta är till hjälp så att inmatningar som skapas från io.IOBase.readlines() resulterar i diffar som är lämpliga att använda med io.IOBase.writelines() eftersom både inmatningar och utmatningar har efterföljande nya rader.

För indata som inte har efterföljande nya rader, sätt lineterm-argumentet till "" så att utdata kommer att vara enhetligt fria från nya rader.

Formatet för context diff har normalt ett huvud för filnamn och ändringstider. Alla eller några av dessa kan anges med strängar för fromfile, tofile, fromfiledate och tofiledate. Modifieringstiderna uttrycks normalt i ISO 8601-format. Om strängarna inte specificeras är standardvärdet blanksteg.

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

Se Ett kommandoradsgränssnitt för difflib för ett mer detaljerat exempel.

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

Returnerar en lista med de bästa ”tillräckligt bra”-matchningarna. word är en sekvens för vilken nära matchningar önskas (vanligtvis en sträng) och possibilities är en lista med sekvenser som word kan matchas mot (vanligtvis en lista med strängar).

Valfritt argument n (standard 3) är det maximala antalet nära matchningar som ska returneras; n måste vara större än 0.

Det valfria argumentet cutoff (standard 0.6) är en float i intervallet [0, 1]. Möjligheter som inte har en poäng som är minst lika stor som ord ignoreras.

De bästa (högst n) matchningarna bland möjligheterna returneras i en lista, sorterade efter likhetspoäng, mest lika först.

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

Jämför a och b (listor med strängar); returnera ett delta i stil med Differ (en generator som genererar deltaraderna).

De valfria nyckelordsparametrarna linejunk och charjunk är filtreringsfunktioner (eller None):

linejunk: En funktion som accepterar ett enda strängargument och returnerar true om strängen är skräp, eller false om den inte är det. Standardvärdet är None. Det finns också en funktion på modulnivå IS_LINE_JUNK(), som filtrerar bort rader utan synliga tecken, förutom högst ett pundtecken ('#') – den underliggande klassen SequenceMatcher gör dock en dynamisk analys av vilka rader som är så frekventa att de utgör brus, och detta fungerar vanligtvis bättre än att använda den här funktionen.

charjunk: En funktion som accepterar ett tecken (en sträng med längden 1) och returnerar om tecknet är skräp, eller false om det inte är det. Standard är modulnivåfunktionen IS_CHARACTER_JUNK(), som filtrerar bort blankstegstecken (ett blanksteg eller en tabb; det är en dålig idé att inkludera newline i detta!)

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

Returnera en av de två sekvenser som genererade ett delta.

Givet en sekvens producerad av Differ.compare() eller ndiff(), extrahera rader som härrör från fil 1 eller 2 (parameter which) och ta bort radprefix.

Exempel:

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

Jämför a och b (listor med strängar); returnerar ett delta (en generator som genererar delta-raderna) i unified diff-format.

Unified diffs är ett kompakt sätt att bara visa de rader som har ändrats plus några rader med sammanhang. Ändringarna visas i en inline-stil (i stället för separata före/efter-block). Antalet kontextrader anges av n som standard är tre.

Som standard skapas kontrollinjerna för diff (de med ---, +++ eller @@) med en efterföljande ny rad. Detta är till hjälp så att inmatningar som skapas från io.IOBase.readlines() resulterar i diffar som är lämpliga att använda med io.IOBase.writelines() eftersom både inmatningar och utmatningar har efterföljande nya rader.

För indata som inte har efterföljande nya rader, sätt lineterm-argumentet till "" så att utdata kommer att vara enhetligt fria från nya rader.

Det enhetliga diff-formatet har normalt ett huvud för filnamn och ändringstider. Alla eller några av dessa kan anges med strängar för fromfile, tofile, fromfiledate och tofiledate. Modifieringstiderna uttrycks normalt i ISO 8601-format. Om strängarna inte specificeras är standardvärdet blanksteg.

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

Se Ett kommandoradsgränssnitt för difflib för ett mer detaljerat exempel.

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

Jämför a och b (listor med bytesobjekt) med hjälp av dfunc; ger en sekvens av deltarader (också bytes) i det format som returneras av dfunc. dfunc måste vara en callable, typiskt antingen unified_diff() eller context_diff().

Gör det möjligt att jämföra data med okänd eller inkonsekvent kodning. Alla indata utom n måste vara bytes-objekt, inte str. Fungerar genom att förlustfritt konvertera alla indata (utom n) till str och anropa dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm). Utdata från dfunc konverteras sedan tillbaka till bytes, så de deltarader som du får har samma okända/inkonsekventa kodning som a och b.

Tillagd i version 3.5.

difflib.IS_LINE_JUNK(line)

Returnerar True för ignorerbara rader. Raden line är ignorerbar om line är tom eller innehåller en enda '#', annars är den inte ignorerbar. Används som standard för parametern linejunk i ndiff() i äldre versioner.

difflib.IS_CHARACTER_JUNK(ch)

Returnerar True för tecken som inte kan ignoreras. Tecknet ch är ignorerbart om ch är ett mellanslag eller en tabb, annars är det inte ignorerbart. Används som standard för parametern charjunk i ndiff().

Se även

Pattern Matching: The Gestalt Approach

Diskussion om en liknande algoritm av John W. Ratcliff och D. E. Metzener. Denna publicerades i Dr. Dobb’s Journal i juli 1988.

SequenceMatcher-objekt

Klassen SequenceMatcher har denna konstruktor:

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

Det valfria argumentet isjunk måste vara None (standard) eller en funktion med ett argument som tar ett sekvenselement och returnerar true om och endast om elementet är ”skräp” och ska ignoreras. Att skicka None för isjunk är likvärdigt med att skicka lambda x: False; med andra ord ignoreras inga element. Till exempel, pass:

lambda x: x i " \t"

om du jämför rader som sekvenser av tecken och inte vill synkronisera med blanksteg eller hårda flikar.

De valfria argumenten a och b är sekvenser som ska jämföras; båda är tomma strängar som standard. Elementen i båda sekvenserna måste vara hashable.

Det valfria argumentet autojunk kan användas för att inaktivera den automatiska skräpheuristiken.

Ändrad i version 3.2: Lagt till parametern autojunk.

SequenceMatcher-objekt får tre dataattribut: bjunk är den uppsättning element i b för vilka isjunk är True; bpopular är den uppsättning element som inte är skräp som anses populära av heuristiken (om den inte är inaktiverad); b2j är en dict som mappar de återstående elementen i b till en lista över positioner där de förekommer. Alla tre återställs när b återställs med set_seqs() eller set_seq2().

Tillagd i version 3.2: Attributen bjunk och bpopulär.

SequenceMatcher-objekt har följande metoder:

set_seqs(a, b)

Ställ in de två sekvenser som ska jämföras.

SequenceMatcher beräknar och lagrar detaljerad information om den andra sekvensen, så om du vill jämföra en sekvens med många sekvenser kan du använda set_seq2() för att ställa in den vanliga sekvensen en gång och anropa set_seq1() upprepade gånger, en gång för var och en av de andra sekvenserna.

set_seq1(a)

Ställ in den första sekvensen som ska jämföras. Den andra sekvensen som ska jämföras ändras inte.

set_seq2(b)

Ställ in den andra sekvensen som ska jämföras. Den första sekvensen som ska jämföras ändras inte.

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

Hitta det längsta matchande blocket i a[alo:ahi] och b[blo:bhi].

Om isjunk utelämnades eller None, returnerar find_longest_match() (i, j, k) så att a[i:i+k] är lika med b[j:j+k], där alo <= i <= i+k <= ahi och blo <= j <= j+k <= bhi. För alla (i', j', k') som uppfyller dessa villkor uppfylls också tilläggsvillkoren k >= k', i <= i', och om i == i', j <= j'. Med andra ord, av alla maximalt matchande block, returnera det som börjar tidigast i a, och av alla de maximalt matchande block som börjar tidigast i a, returnera det som börjar tidigast i b.

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

Om isjunk har angetts, bestäms först det längsta matchande blocket enligt ovan, men med den ytterligare begränsningen att inget junk-element får förekomma i blocket. Sedan förlängs det blocket så långt som möjligt genom att matcha (endast) skräpelement på båda sidor. Det resulterande blocket matchar alltså aldrig på skräp utom när identiskt skräp råkar ligga intill en intressant matchning.

Här är samma exempel som tidigare, men där blanksteg betraktas som skräp. Det hindrar ' abcd' från att matcha ' abcd' i slutet av den andra sekvensen direkt. Istället är det bara 'abcd' som kan matcha, och den matchar 'abcd' längst till vänster i den andra sekvensen:

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

Om inga block matchar returneras (alo, blo, 0).

Denna metod returnerar en namngiven tupel Match(a, b, size).

Ändrad i version 3.9: Lagt till standardargument.

get_matching_blocks()

Returnerar en lista med tripplar som beskriver icke-överlappande matchande undersekvenser. Varje trippel är av formen (i, j, n) och betyder att a[i:i+n] == b[j:j+n]. Tripplarna är monotont ökande i i och j.

Den sista trippeln är en dummy och har värdet (len(a), len(b), 0). Det är den enda trippeln med n == 0. Om (i, j, n) och (i', j', n') är angränsande tripplar i listan och den andra inte är den sista trippeln i listan, så är i+n < i' eller j+n < j'; med andra ord beskriver angränsande tripplar alltid icke angränsande lika block.

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

Returnerar en lista med 5-tupler som beskriver hur man förvandlar a till b. Varje tupel är av formen (tag, i1, i2, j1, j2). Den första tupeln har i1 == j1 == 0, och de återstående tuplarna har i1 lika med i2 från föregående tupel, och på samma sätt är j1 lika med föregående j2.

tag-värdena är strängar med följande betydelser:

Värde

Betydelse

'replace'

a[i1:i2] bör ersättas med b[j1:j2].

'delete'

a[i1:i2] bör tas bort. Observera att j1 == j2 i detta fall.

'insert'

b[j1:j2] bör infogas vid a[i1:i1]. Observera att i1 == i2 i detta fall.

'equal'

a[i1:i2] == b[j1:j2] (undersekvenserna är lika).

Till exempel:

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

Returnerar en generator av grupper med upp till n rader av kontext.

Med utgångspunkt från de grupper som returneras av get_opcodes(), delar denna metod upp mindre förändringskluster och eliminerar mellanliggande intervall som inte har några förändringar.

Grupperna returneras i samma format som get_opcodes().

ratio()

Returnerar ett mått på sekvensernas likhet som en flottör i intervallet [0, 1].

Där T är det totala antalet element i båda sekvenserna och M är antalet matchningar, är detta 2,0 * M / T. Observera att detta är ”1,0” om sekvenserna är identiska och ”0,0” om de inte har något gemensamt.

Detta är dyrt att beräkna om get_matching_blocks() eller get_opcodes() inte redan har anropats, i vilket fall du kanske vill prova quick_ratio() eller real_quick_ratio() först för att få en övre gräns.

Anteckning

Varning för detta: Resultatet av ett ratio()-anrop kan bero på argumentens ordning. Till exempel:

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

Återge en övre gräns för ratio() relativt snabbt.

real_quick_ratio()

Returnera en övre gräns för ratio() mycket snabbt.

De tre metoder som returnerar förhållandet mellan matchande och totala tecken kan ge olika resultat på grund av olika approximationsnivåer, även om quick_ratio() och real_quick_ratio() alltid är minst lika stora som ratio():

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

Exempel på SequenceMatcher

I det här exemplet jämförs två strängar, där blanksteg betraktas som ”skräp”:

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

ratio() returnerar en float i [0, 1], som mäter likheten mellan sekvenserna. Som en tumregel innebär ett ratio()-värde över 0,6 att sekvenserna är nära matchningar:

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

Om du bara är intresserad av var sekvenserna matchar är get_matching_blocks() praktiskt:

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

Observera att den sista tupeln som returneras av get_matching_blocks() alltid är en dummy, (len(a), len(b), 0), och detta är det enda fallet där det sista tupelelementet (antal matchade element) är 0.

Om du vill veta hur du ändrar den första sekvensen till den andra använder du 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]

Se även

Skillnad mellan objekt

Observera att Differ -genererade deltan inte gör anspråk på att vara minimala differenser. Tvärtom är minimala skillnader ofta kontraintuitiva, eftersom de synkroniserar överallt där det är möjligt, ibland oavsiktliga matchningar med 100 sidors mellanrum. Genom att begränsa synkroniseringspunkterna till sammanhängande matchningar bevaras en viss lokalitet, men ibland till priset av en längre diff.

Klassen Differ har denna konstruktor:

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

De valfria nyckelordsparametrarna linejunk och charjunk är för filterfunktioner (eller None):

linejunk: En funktion som accepterar ett enda strängargument och returnerar true om strängen är skräp. Standardvärdet är None, vilket innebär att ingen rad betraktas som skräp.

charjunk: En funktion som accepterar ett enda teckenargument (en sträng med längden 1) och returnerar true om tecknet är skräp. Standardvärdet är None, vilket innebär att inget tecken betraktas som skräp.

Dessa skräpfiltreringsfunktioner påskyndar matchningen för att hitta skillnader och gör inte att några avvikande rader eller tecken ignoreras. Läs beskrivningen av find_longest_match()-metodens parameter isjunk för en förklaring.

Differ-objekt används (deltan genereras) via en enda metod:

compare(a, b)

Jämför två sekvenser av linjer och generera deltaet (en sekvens av linjer).

Varje sekvens måste innehålla enskilda enradiga strängar som slutar med nya linjer. Sådana sekvenser kan erhållas från metoden readlines() för filliknande objekt. Det genererade deltat består också av strängar som avslutas med nya rader, redo att skrivas ut som de är via metoden writelines() för ett filliknande objekt.

Skillnad Exempel

I detta exempel jämförs två texter. Först ställer vi in texterna, sekvenser av enskilda enradiga strängar som slutar med nya rader (sådana sekvenser kan också erhållas från readlines()-metoden för filliknande objekt):

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

Därefter instansierar vi ett Differ-objekt:

>>> d = Differ()

Observera att när vi instansierar ett Differ-objekt kan vi skicka funktioner för att filtrera bort ”skräp” i rader och tecken Se Differ()-konstruktören för detaljer.

Slutligen jämför vi de två:

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

result är en lista med strängar, så låt oss pretty-printa den:

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

Som en enda sträng med flera rader ser det ut så här:

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

Ett kommandoradsgränssnitt för difflib

Detta exempel visar hur man använder difflib för att skapa ett diff-liknande verktyg.

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

ndiff exempel

Detta exempel visar hur man använder 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:])