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 argumentetautojunk
tillFalse
när du skaparSequenceMatcher
.Ä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 avHtmlDiff
för att generera HTML-skillnader sida vid sida). Sendiff()
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ärdetFalse
för att visa de fullständiga filerna. numlines är som standard5
. När context ärTrue
styr numlines antalet kontextlinjer som omger de markerade skillnaderna. När kontext ärFalse
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ånio.IOBase.readlines()
resulterar i diffar som är lämpliga att använda medio.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 än0
.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 klassenSequenceMatcher
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()
ellerndiff()
, 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ånio.IOBase.readlines()
resulterar i diffar som är lämpliga att använda medio.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()
ellercontext_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 indiff()
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 indiff()
.
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 skickaNone
för isjunk är likvärdigt med att skickalambda 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 medset_seqs()
ellerset_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ändaset_seq2()
för att ställa in den vanliga sekvensen en gång och anropaset_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]
ochb[blo:bhi]
.Om isjunk utelämnades eller
None
, returnerarfind_longest_match()
(i, j, k)
så atta[i:i+k]
är lika medb[j:j+k]
, däralo <= i <= i+k <= ahi
ochblo <= j <= j+k <= bhi
. För alla(i', j', k')
som uppfyller dessa villkor uppfylls också tilläggsvillkorenk >= k'
,i <= i'
, och omi == 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 atta[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 medn == 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å äri+n < i'
ellerj+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 hari1 == 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 medb[j1:j2]
.'delete'
a[i1:i2]
bör tas bort. Observera attj1 == j2
i detta fall.'insert'
b[j1:j2]
bör infogas vida[i1:i1]
. Observera atti1 == 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()
ellerget_opcodes()
inte redan har anropats, i vilket fall du kanske vill provaquick_ratio()
ellerreal_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
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
Funktionen
get_close_matches()
i denna modul visar hur enkel kod som bygger påSequenceMatcher
kan användas för att göra användbart arbete.Enkelt recept för versionshantering för en liten applikation byggd med
SequenceMatcher
.
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 metodenwritelines()
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:])