difflib --- 差分の計算の補助

ソースコード: Lib/difflib.py


This module provides classes and functions for comparing sequences. Most of them compare sequences of text lines (for example lists of strings, or file objects) and produce diffs -- reports on the differences. Diffs can be produced in in various formats, including HTML and context and unified diffs -- formats produced by tools like diff and git diff.

Comparisons are done using a matching algorithm implemented in SequenceMatcher -- a flexible class for comparing pairs of sequences of any type, not just text, so long as the sequence elements are hashable.

Junk heuristic

difflib uses a junk heuristic: some items are deemed to be junk, and ignored when searching for similarities. Ideally, these are uninteresting or common items, such as blank lines or whitespace.

This heuristic can speed the algorithm up (because it reduces the number of possible combinations) and it can produce results that are more understandable for humans (typically breaking on whitespace). But it can also cause pathological cases:

  • Inappropriately chosen junk items can cause an unexpectedly large (but still correct) result.

  • The default heuristic is asymmetric: only the second sequence is inspected when determining what is considered junk, so comparing A to B can give different results than comparing B to A and reversing the result.

By default, if the second input sequence is at least 200 items long, items that account for more than 1% it are considered junk.

Depending on your data, you should consider turning this heuristic off (setting SequenceMatcher's autojunk argument to to False) or tuning it (using the isjunk argument, perhaps to one of the predefined functions).

The difflib algorithm

The algorithm used in SequenceMatcher predates, and is a little fancier than, an algorithm published in the late 1980s by Ratcliff and Obershelp under the hyperbolic name "gestalt pattern matching." The idea is to find the longest contiguous subsequence common to both inputs, then recursively handle the pieces of the sequences to the left and to the right of the matching subsequence.

参考

Pattern Matching: The Gestalt Approach

Discussion of a similar algorithm by John W. Ratcliff and D. E. Metzener. This was published in Dr. Dobb's Journal in July, 1988.

As an extension to the Ratcliff and Obershelp algorithm, difflib searches for the longest junk-free contiguous subsequence. See the Junk heuristic section for details.

CPython 実装の詳細: Timing

The basic Ratcliff-Obershelp algorithm is cubic time in the worst case and quadratic time in the expected case. difflib's algorithm is quadratic time for the worst case and has expected-case behavior dependent in a complicated way on how many elements the sequences have in common; best case time is linear.

Diff generation

class difflib.Differ

テキスト行からなるシーケンスを比較するクラスです。人が読むことのできる差分を作成します。 Differ クラスは SequenceMatcher クラスを利用して、行からなるシーケンスを比較したり、(ほぼ)同一の行内の文字を比較したりします。

Differ クラスによる差分の各行は、2文字のコードで始まります:

コード

意味

'- '

行はシーケンス1にのみ存在する

'+ '

行はシーケンス2にのみ存在する

'  '

行は両方のシーケンスで同一

'? '

行は入力シーケンスのどちらにも存在しない

Lines beginning with '?' attempt to guide the eye to intraline differences, and were not present in either input sequence. These lines can be confusing if the sequences contain whitespace characters, such as spaces, tabs or line breaks.

Note that Differ-generated deltas make no claim to be minimal diffs. To the contrary, minimal diffs are often counter-intuitive for humans, because they synch up anywhere possible, sometimes at accidental matches 100 pages apart. Restricting synch points to contiguous matches preserves some notion of locality, at the occasional cost of producing a longer diff.

Differ は、以下のようなコンストラクタを持ちます:

__init__(linejunk=None, charjunk=None)

オプションのキーワードパラメータ linejunkcharjunk は、フィルタ関数を渡します (使わないときは None):

linejunk: ひとつの文字列引数を受け取る関数です。文字列が junk のときに真を返します。デフォルトでは、None であり、どんな行であっても junk とは見なされません。

charjunk: この関数は文字(長さ1の文字列)を引数として受け取り、文字が junk であるときに真を返します。デフォルトは None であり、どんな文字も junk とは見なされません。

これらの junk フィルター関数により、差分を発見するマッチングが高速化し、差分の行や文字が無視されることがなくなります。説明については、 find_longest_match() メソッドの isjunk 引数の説明をご覧ください。

Differ オブジェクトは、以下の1つのメソッドを通して利用されます。(差分を生成します):

compare(a, b)

文字列からなる2つのシーケンスを比較し、差分(を表す文字列からなるシーケンス)を生成します。

Each sequence must contain individual single-line strings ending with newlines. Such sequences can be obtained from the readlines() method of file-like objects. The generated delta also consists of newline-terminated strings, ready to be printed as-is via the writelines() method of a file-like object.

class difflib.HtmlDiff

このクラスは、二つのテキストを左右に並べて比較表示し、行間あるいは行内の変更点を強調表示するような HTML テーブル (またはテーブルの入った完全な HTML ファイル) を生成するために使います。テーブルは完全差分モード、コンテキスト差分モードのいずれでも生成できます。

警告

The trailing newlines get stripped before the diff, so the result can be incomplete. See gh-71896 for details.

このクラスのコンストラクタは以下のようになっています:

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

HtmlDiff のインスタンスを初期化します。

tabsize はオプションのキーワード引数で、タブストップ幅を指定します。デフォルトは 8 です。

wrapcolumn はオプションのキーワード引数で、テキストを折り返すカラム幅を指定します。デフォルトは None で折り返しを行いません。

linejunk および charjunk はオプションのキーワード引数で、 ndiff() (HtmlDiff はこの関数を使って左右のテキストの差分を HTML で生成します) に渡されます。それぞれの引数のデフォルト値および説明は ndiff() のドキュメントを参照してください。

以下のメソッドが public になっています:

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

fromlinestolines (いずれも文字列のリスト) を比較し、行間または行内の変更点が強調表示された行差分の入った表を持つ完全な HTML ファイルを文字列で返します。

fromdesc および todesc はオプションのキーワード引数で、差分表示テーブルにおけるそれぞれ差分元、差分先ファイルのカラムのヘッダになる文字列を指定します (いずれもデフォルト値は空文字列です)。

context および numlines はともにオプションのキーワード引数です。contextTrue にするとコンテキスト差分を表示し、デフォルトの False にすると完全なファイル差分を表示します。numlines のデフォルト値は 5 で、contextTrue の場合、numlines は強調部分の前後にあるコンテキスト行の数を制御します。contextFalse の場合、numlines は "next" と書かれたハイパーリンクをたどった時に到達する場所が次の変更部分より何行前にあるかを制御します (値をゼロにした場合、"next" ハイパーリンクを辿ると変更部分の強調表示がブラウザの最上部に表示されるようになります)。

注釈

fromdesctodesc はエスケープされていないHTMLとして解釈されます。信頼できないソースからの入力を受け取る際には適切にエスケープされるべきです。

バージョン 3.5 で変更: charset キーワード専用引数が追加されました。HTML 文書のデフォルトの文字集合が 'ISO-8859-1' から 'utf-8' に変更されました。

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

fromlinestolines (いずれも文字列のリスト) を比較し、行間または行内の変更点が強調表示された行差分の入った完全な HTML テーブルを文字列で返します。

このメソッドの引数は、 make_file() メソッドの引数と同じです。

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

ab (文字列のリスト) を比較し、差分 (差分形式の行を生成する ジェネレータ) を、 context diff のフォーマット(以下「コンテキスト形式」)で返します。

コンテキスト形式は、変更があった行に前後数行を加えてある、コンパクトな表現方法です。変更箇所は、変更前/変更後に分けて表します。コンテキスト (変更箇所前後の行) の行数は n で指定し、デフォルト値は 3 です。

デフォルトで、 diff 制御行 (***--- を含む行) は改行付きで生成されます。 io.IOBase.readlines() で作られた入力が io.IOBase.writelines() で扱うのに適した diff になるので (なぜなら入力と出力の両方が改行付きのため) 、これは有用です。

行末に改行文字を持たない入力に対しては、出力でも改行文字を付加しないように lineterm 引数に "" を渡してください。

コンテキスト形式は、通常、ヘッダにファイル名と変更時刻を持っています。この情報は、文字列 fromfile, tofile, fromfiledate, tofiledate で指定できます。変更時刻の書式は、通常、ISO 8601 フォーマットで表されます。指定しなかった場合のデフォルト値は、空文字列です。

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

より詳細な例は、 difflib のコマンドラインインターフェース を参照してください。

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

「十分」なマッチの上位のリストを返します。word はマッチさせたいシーケンス (大概は文字列) です。possibilitiesword にマッチさせるシーケンスのリスト (大概は文字列のリスト) です。

オプションの引数 n (デフォルトでは 3)はメソッドの返すマッチの最大数です。n0 より大きくなければなりません。

オプションの引数 cutoff (デフォルトでは 0.6)は、区間 [0, 1] に入る小数の値です。word との一致率がそれ未満の possibilities の要素は無視されます。

possibilities の要素でマッチした上位(多くても 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)

ab (文字列のリスト) を比較し、差分 (差分形式の行を生成する ジェネレータ) を、 Differ のスタイルで返します。

オプションのキーワード引数 linejunkcharjunk には、フィルタ関数 (または None) を渡します。

linejunk: A function that accepts a single string argument, and returns true if the string is junk, or false if not. The default is None. There is also a module-level function IS_LINE_JUNK(), which filters out lines without visible characters, except for at most one hash character ('#') -- however the underlying SequenceMatcher class does a dynamic analysis of which lines are so frequent as to constitute noise, and this usually works better than using this function.

charjunk: 文字 (長さ1の文字列) を受け取る関数です。文字列が junk の場合は真を、そうでない場合は偽を返します。デフォルトでは、モジュールレべルの関数 IS_CHARACTER_JUNK() であり、これは空白文字類 (空白またはタブ、改行文字をこれに含めてはいけません) をフィルタして排除します。

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

ab (文字列のリスト) を比較し、差分 (差分形式の行を生成する ジェネレータ) を、 unified diff フォーマット(以下「ユニファイド形式」)で返します。

ユニファイド形式は変更があった行にコンテキストとなる前後数行を加えた、コンパクトな表現方法です。変更箇所は (変更前/変更後を分離したブロックではなく) インラインスタイルで表されます。コンテキストの行数は、n で指定し、デフォルト値は 3 です。

デフォルトで、 diff 制御行 (---, +++, @@ を含む行) は改行付きで生成されます。 io.IOBase.readlines() で作られた入力が io.IOBase.writelines() で扱うのに適した diff になるので (なぜなら入力と出力の両方が改行付きのため) 、これは有用です。

行末に改行文字を持たない入力に対しては、出力でも改行文字を付加しないように lineterm 引数に "" を渡してください。

The unified diff format normally has a header for filenames and modification times. Any or all of these may be specified using strings for fromfile, tofile, fromfiledate, and tofiledate. The modification times are normally expressed in the ISO 8601 format. If not specified, the strings default to blanks.

>>> 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 のコマンドラインインターフェース を参照してください。

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

dfunc を使用して ab (bytes オブジェクトのリスト) を比較して、差分形式の行 (これも bytes オブジェクトです) を*dfunc* の戻り値の形式で返します。dfunc は、呼び出し可能である必要があります。一般に、これは unified_diff() または context_diff() です。

未知のエンコーディングまたは一貫性のないエンコーディングのデータ同士を比較できます。n 以外のすべての入力は、bytes オブジェクトである必要があります。n 以外のすべての入力を損失なく str に変換して、dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm) を呼び出すことにより動作します。dfunc の出力は、bytes 型に変換されます。これにより、受け取る差分形式の行のエンコーディングは、ab の未知または一貫性のないエンコーディングと同一になります。

Added in version 3.5.

Junk definition functions

difflib.IS_LINE_JUNK(line)

無視できる行のとき True を返します。行 line は空白、または '#' ひとつのときに無視できます。それ以外のときには無視できません。古いバージョンでは ndiff() の引数 linejunk にデフォルトで使用されました。

difflib.IS_CHARACTER_JUNK(ch)

無視できる文字のとき True を返します。文字 ch が空白、またはタブ文字のときには無視できます。それ以外の時には無視できません。 ndiff() の引数 charjunk としてデフォルトで使用されます。

SequenceMatcher objects

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

オプションの引数 isjunk は、None (デフォルトの値です) にするか、単一の引数をとる関数でなければなりません。後者の場合、関数はシーケンスの要素を受け取り、要素が junk であり、無視すべきである場合に限り真を返すようにしなければなりません。isjunkNone を渡すと、lambda x: False を渡したのと同じになります; すなわち、いかなる要素も無視しなくなります。例えば以下のような引数を渡すと:

lambda x: x in " \t"

空白とタブ文字を無視して文字のシーケンスを比較します。

オプションの引数 ab は、比較される文字列で、デフォルトでは空の文字列です。両方のシーケンスの要素は、 ハッシュ可能 である必要があります。

オプションの引数 autojunk は、自動 junk ヒューリスティックを無効にするために使えます。

バージョン 3.2 で変更: Added the autojunk parameter.

SequenceMatcher オブジェクトは3つのデータ属性を持っています: bjunk は、 isjunkTrue であるような b の要素の集合です; bpopular は、 (無効でなければ) ヒューリスティックによって popular であると考えられる非ジャンク要素の集合です; b2j は、 b の残りの要素をそれらが生じる位置のリストに写像する dict です。この 3 つは set_seqs() または set_seq2()b がリセットされる場合は常にリセットされます。

Added in version 3.2: bjunk および bpopular 属性。

SequenceMatcher オブジェクトは以下のメソッドを持ちます:

set_seqs(a, b)

比較される2つの文字列を設定します。

SequenceMatcher オブジェクトは、2つ目のシーケンスについての詳細な情報を計算し、キャッシュします。 1つのシーケンスをいくつものシーケンスと比較する場合、まず set_seq2() を使って文字列を設定しておき、別の文字列を1つずつ比較するために、繰り返し set_seq1() を呼び出します。

set_seq1(a)

比較を行う1つ目のシーケンスを設定します。比較される2つ目のシーケンスは変更されません。

set_seq2(b)

比較を行う2つ目のシーケンスを設定します。比較される1つ目のシーケンスは変更されません。

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

a[alo:ahi]b[blo:bhi] の中から、最長のマッチ列を探します。

isjunk が省略されたか None の時、 find_longest_match()a[i:i+k]b[j:j+k] と等しいような (i, j, k) を返します。その値は alo <= i <= i+k <= ahi かつ blo <= 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) を返します。

バージョン 3.9 で変更: デフォルト引数が追加されました。

get_matching_blocks()

マッチした互いに重複の無いシーケンスを表す、3つ組の値のリストを返します。 それぞれの値は (i, j, n) という形式で表され、a[i:i+n] == b[j:j+n] という関係を意味します。3つの値は ij の間で単調に増加します。

最後の3つ組はダミーで、(len(a), len(b), 0) という値を持ちます。これは n == 0 である唯一のタプルです。もし (i, j, n)(i', j', n') がリストで並んでいる3つ組で、2つ目が最後の3つ組でなければ、i+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()

ab にするための方法を記述する5つのタプルを返します。それぞれのタプルは (tag, i1, i2, j1, j2) という形式であらわされます。最初のタプルは i1 == j1 == 0 であり、i1 はその前にあるタプルの i2 と同じ値です。同様に j1 は前の j2 と同じ値になります。

tag の値は文字列であり、次のような意味です:

意味

'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 行までのコンテキストを含むグループを生成するような、 ジェネレータ を返します。

このメソッドは、 get_opcodes() で返されるグループの中から、似たような差異のかたまりに分け、間に挟まっている変更の無い部分を省きます。

グループは get_opcodes() と同じ書式で返されます。

ratio()

[0, 1] の範囲の浮動小数点数で、シーケンスの類似度を測る値を返します。

T が2つのシーケンスの要素数の総計だと仮定し、M をマッチした数とすると、この値は 2.0*M / T であらわされます。もしシーケンスがまったく同じ場合、値は 1.0 となり、まったく異なる場合には 0.0 となります。

このメソッドは get_matching_blocks() または get_opcodes() がまだ呼び出されていない場合には非常にコストが高いです。この場合、上限を素早く計算するために、 quick_ratio() もしくは real_quick_ratio() を最初に試してみる方がいいかもしれません。

quick_ratio()

ratio() の上界を、より高速に計算します。

real_quick_ratio()

ratio() の上界を、非常に高速に計算します。

The three methods that return the ratio of matching to total characters can give different results due to differing levels of approximation, although quick_ratio() and real_quick_ratio() are always at least as large as ratio():

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

使用例

SequenceMatcher examples

この例は2つの文字列を比較します。空白を "junk" とします:

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

ratio() returns a float in [0, 1], measuring the similarity of the sequences. As a rule of thumb, a ratio() value over 0.6 means the sequences are close matches:

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

If you're only interested in where the sequences match, get_matching_blocks() is handy:

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

Note that the last tuple returned by get_matching_blocks() is always a dummy, (len(a), len(b), 0), and this is the only case in which the last tuple element (number of elements matched) is 0.

If you want to know how to change the first sequence into the second, use 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]

参考

Differ example

This example compares two texts. First we set up the texts, sequences of individual single-line strings ending with newlines (such sequences can also be obtained from the readlines() method of file-like objects):

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

注意: Differ オブジェクトをインスタンス化するとき、行 junk と文字 junk をフィルタリングする関数を渡すことができます。詳細は 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.

difflib のコマンドラインインターフェース

This example shows how to use difflib to create a diff-like utility.

""" 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
import datetime as dt

def file_mtime(path):
    t = dt.datetime.fromtimestamp(os.stat(path).st_mtime,
                                  dt.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 example

This example shows how to use 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:])