6.3. difflib
HTML context diff, unified diff filecmp
-
class
difflib.SequenceMatcher (hashable)1980 Ratcliff Obershelp """junk" "junk" (junk Ratcliff Obershelp )
: Ratcliff-Obershelp 32
SequenceMatcher2junk :
SequenceMatcherjunk () 1% 200 "popular" junkSequenceMatcherautojunkFalse3.2 : autojunk
-
class
difflib.Differ Differ
SequenceMatcher()Differ2:'- '1 '+ '2 ' ''? ''
?'
-
class
difflib.HtmlDiff HTML ( HTML )
:
-
__init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK) -
tabsize
8wrapcolumn
None
public :
-
make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5, *, charset='utf-8') fromlines tolines () HTML
fromdesc todesc ()
context numlines context
TrueFalsenumlines5contextTruenumlines contextFalsenumlines "next" ("next" )3.5 : charset HTML
'ISO-8859-1''utf-8'
-
make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5) fromlines tolines () HTML
Tools/scripts/diff.py-
-
difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n') a b () ((generator)) context diff ()
/ () n 3
diff (
***---)io.IOBase.readlines()io.IOBase.writelines()diff ()lineterm
""fromfile, tofile, fromfiledate, tofiledate ISO 8601
>>> 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
-
difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6) word () possibilities word ()
n (
3)n0cutoff (
0.6) [0, 1] word possibilitiespossibilities ( n )()
>>> 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) -
linejunk charjunk (
None)linejunk: 1 junk
NoneIS_LINE_JUNK()1 ('#')SequenceMatchercharjunk: (1) junk
IS_CHARACTER_JUNK()()Tools/scripts/ndiff.py>>> 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) -
Differ.compare()ndiff()sequence 1 2 ( which ):
>>> 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') a b () ((generator)) unified diff ()
(/) n 3
diff (
---,+++,@@)io.IOBase.readlines()io.IOBase.writelines()diff ()lineterm
""fromfile, tofile, fromfiledate, tofiledate ISO 8601
>>> 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
-
difflib.diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n') dfunc a b (bytes ) ( bytes ) *dfunc* dfunc
unified_diff()context_diff()n bytes n str
dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)dfunc bytes a b3.5 .
-
difflib.IS_LINE_JUNK(line) line
'#'ndiff()linejunk
-
difflib.IS_CHARACTER_JUNK(ch) ch
ndiff()charjunk
- Pattern Matching: The Gestalt Approach
- John W. Ratcliff D. E. Metzener Dr. Dobbs Journal 19887
6.3.1. SequenceMatcher
-
class
difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True) isjunk
None() junk isjunkNonelambda x: 0; :lambda x: x in " \t"
a b (hashable)
autojunk junk
3.2 : autojunk
SequenceMatcher 3: bjunk isjunk
Trueb ; bpopular () popular ; b2j b dict 3set_seqs()set_seq2()b3.2 : bjunk bpopular
SequenceMatcher2 1set_seq2()1set_seq1()-
find_longest_match(alo, ahi, blo, bhi) a[alo:ahi]b[blo:bhi]isjunk
Nonefind_longest_match()a[i:i+k]b[j:j+k](i, j, k)alo <= i <= i+k <= ahiblo <= j <= j+k <= bhi(i', j', k')k >= k',i <= i'i == i',j <= j'a a b>>> s = SequenceMatcher(None, " abcd", "abcd abcd") >>> s.find_longest_match(0, 5, 0, 9) Match(a=0, b=4, size=5)
isjunk junk junk junk junk
junk
' abcd'2' abcd''abcd'2'abcd':>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd") >>> s.find_longest_match(0, 5, 0, 9) Match(a=1, b=0, size=4)
(alo, blo, 0)named tuple
Match(a, b, size)
-
get_matching_blocks() 3
(i, j, n)a[i:i+n] == b[j:j+n]3 i j(len(a), len(b), 0)n == 0(i, j, n)(i', j', n')323i+n != i'j+n != j'3>>> 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() a b 5
(tag, i1, i2, j1, j2)i1 == j1 == 0i1 i2 j1 j2tag :
'replace'a[i1:i2]b[j1:j2]'delete'a[i1:i2]j1 == j2'insert'b[j1:j2]a[i1:i1]i1 == i2'equal'a[i1:i2] == b[j1:j2]().:
>>> 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) n (generator)
-
ratio() [0, 1]
T 2M 2.0*M / T
1.00.0get_matching_blocks()get_opcodes()quick_ratio()real_quick_ratio()
-
3 quick_ratio() real_quick_ratio() ratio() :
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0
6.3.2. SequenceMatcher
2 "junk" :
>>> s = SequenceMatcher(lambda x: x == " ",
... "private Thread currentThread;",
... "private volatile Thread currentThread;")
ratio() [0, 1] ratio() 0.6:
>>> print(round(s.ratio(), 3))
0.866
get_matching_blocks() :
>>> 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
get_matching_blocks() (len(a), len(b), 0) 0
2 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]
6.3.3. Differ
Differ 100
Differ :
-
class
difflib.Differ(linejunk=None, charjunk=None) linejunk charjunk (
None):linejunk: junk
Nonejunkcharjunk: (1) junk
Nonejunkjunk
find_longest_match()isjunkDiffer1:
6.3.4. Differ
2 ( readlines() ):
>>> 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)
Differ :
>>> d = Differ()
2:
>>> result = list(d.compare(text1, text2))
result pretty-print:
>>> 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']
:
>>> 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.
6.3.5. difflib
difflib diff Python Tools/scripts/diff.py
#!/usr/bin/env python3
""" Command line interface to difflib.py providing diffs in four formats:
* ndiff: lists every line and highlights interline changes.
* context: highlights clusters of changes in a before/after format.
* unified: highlights clusters of changes in an inline format.
* html: generates side by side comparison with change highlights.
"""
import sys, os, difflib, argparse
from datetime import datetime, timezone
def file_mtime(path):
t = datetime.fromtimestamp(os.stat(path).st_mtime,
timezone.utc)
return t.astimezone().isoformat()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-c', action='store_true', default=False,
help='Produce a context format diff (default)')
parser.add_argument('-u', action='store_true', default=False,
help='Produce a unified format diff')
parser.add_argument('-m', action='store_true', default=False,
help='Produce HTML side by side diff '
'(can use -c and -l in conjunction)')
parser.add_argument('-n', action='store_true', default=False,
help='Produce a ndiff format diff')
parser.add_argument('-l', '--lines', type=int, default=3,
help='Set number of context lines (default 3)')
parser.add_argument('fromfile')
parser.add_argument('tofile')
options = parser.parse_args()
n = options.lines
fromfile = options.fromfile
tofile = options.tofile
fromdate = file_mtime(fromfile)
todate = file_mtime(tofile)
with open(fromfile) as ff:
fromlines = ff.readlines()
with open(tofile) as tf:
tolines = tf.readlines()
if options.u:
diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
elif options.n:
diff = difflib.ndiff(fromlines, tolines)
elif options.m:
diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n)
else:
diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
sys.stdout.writelines(diff)
if __name__ == '__main__':
main()