difflib —デルタを計算するためのヘルパー—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.8/library/difflib
移動先:案内検索

difflib —デルタを計算するためのヘルパー

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



このモジュールは、シーケンスを比較するためのクラスと関数を提供します。 たとえば、ファイルの比較に使用でき、HTMLやコンテキスト、統一された差分など、さまざまな形式のファイルの違いに関する情報を生成できます。 ディレクトリとファイルの比較については、 filecmp モジュールも参照してください。

class difflib.SequenceMatcher

これは、シーケンス要素がハッシュ可能である限り、任意のタイプのシーケンスのペアを比較するための柔軟なクラスです。 基本的なアルゴリズムは、1980年代後半にRatcliffとObershelpによって双曲線の名前「gestaltpatternmatching」で公開されたアルゴリズムよりも前のものであり、少し凝っています。 アイデアは、「ジャンク」要素を含まない、最も長く連続して一致するサブシーケンスを見つけることです。 これらの「ジャンク」要素は、空白行や空白など、ある意味で面白くない要素です。 (ジャンクの処理は、Ratcliff and Obershelpアルゴリズムの拡張です。)次に、同じアイデアが、一致するサブシーケンスの左側と右側のシーケンスの断片に再帰的に適用されます。 これは最小限の編集シーケンスを生成しませんが、人々に「正しく見える」一致を生成する傾向があります。

タイミング:基本的なRatcliff-Obershelpアルゴリズムは、最悪の場合は3次時間、予想される場合は2次時間です。 SequenceMatcher は、最悪の場合の2次時間であり、シーケンスに共通する要素の数に複雑な方法で依存する予想されるケースの動作があります。 最良の場合の時間は線形です。

自動ジャンクヒューリスティック: SequenceMatcher supports a heuristic that automatically treats certain sequence items as junk. The heuristic counts how many times each individual item appears in the sequence. If an item’s duplicates (after the first one) account for more than 1% of the sequence and the sequence is at least 200 items long, this item is marked as “popular” and is treated as junk for the purpose of sequence matching. This heuristic can be turned off by setting the autojunk argument to False when creating the SequenceMatcher.

バージョン3.2の新機能: autojunk パラメーター。

class difflib.Differ

これは、テキスト行のシーケンスを比較し、人間が読める形式の違いまたはデルタを生成するためのクラスです。 Differは、 SequenceMatcher を使用して、行のシーケンスを比較することと、類似した(ほぼ一致する)行内の文字のシーケンスを比較することの両方を行います。

Different デルタの各行は、2文字のコードで始まります。

コード

意味

'- '

シーケンス1に固有の行

'+ '

シーケンス2に固有の行

'  '

両方のシーケンスに共通の行

'? '

どちらの入力シーケンスにも行がありません

'?'で始まる行は、行内の違いに目を誘導しようとしますが、どちらの入力シーケンスにも存在しませんでした。 シーケンスにタブ文字が含まれている場合、これらの行は混乱する可能性があります。

class difflib.HtmlDiff

このクラスを使用して、テキストと行間および行内の変更のハイライトとの行ごとの比較を並べて表示するHTMLテーブル(またはテーブルを含む完全なHTMLファイル)を作成できます。 テーブルは、完全モードまたはコンテキスト差分モードのいずれかで生成できます。

このクラスのコンストラクターは次のとおりです。

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

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

tabsize は、タブストップの間隔を指定するためのオプションのキーワード引数であり、デフォルトは8です。

wrapcolumn は、行が分割されて折り返される列番号を指定するオプションのキーワードです。デフォルトでは、行が折り返されないNoneになります。

linejunk および charjunk は、 ndiff()に渡されるオプションのキーワード引数です( HtmlDiff がHTMLの違いを並べて生成するために使用します)。 引数のデフォルト値と説明については、 ndiff()のドキュメントを参照してください。

次のメソッドは公開されています。

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

fromlinestolines (文字列のリスト)を比較し、行間および行内の変更が強調表示された行ごとの違いを示すテーブルを含む完全なHTMLファイルである文字列を返します。

fromdesc および todesc は、ファイルの列ヘッダー文字列から/へを指定するためのオプションのキーワード引数です(どちらもデフォルトで空の文字列です)。

contextnumlines はどちらもオプションのキーワード引数です。 コンテキストの違いを表示する場合は、 contextTrueに設定します。それ以外の場合は、ファイル全体を表示するためのデフォルトはFalseです。 numlines のデフォルトは5です。 contextTrueの場合、 numlines は、差異のハイライトを囲むコンテキスト行の数を制御します。 contextFalseの場合 numlines は、「次の」ハイパーリンクを使用するときに差分ハイライトの前に表示される行数を制御します(ゼロに設定すると「次の」 」ハイパーリンクを使用して、先行するコンテキストなしで次の違いのハイライトをブラウザの上部に配置します)。

ノート

fromdesc および todesc は、エスケープされていないHTMLとして解釈されるため、信頼できないソースからの入力を受信している間は適切にエスケープする必要があります。

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

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

fromlinestolines (文字列のリスト)を比較し、行間および行内の変更が強調表示された行ごとの違いを示す完全なHTMLテーブルである文字列を返します。

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

Tools/scripts/diff.pyは、このクラスのコマンドラインフロントエンドであり、その使用例が含まれています。

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

ab (文字列のリスト)を比較します。 デルタ(デルタラインを生成するジェネレーター)をコンテキスト差分形式で返します。

コンテキスト差分は、変更された行と数行のコンテキストだけを表示するコンパクトな方法です。 変更は、変更前/変更後のスタイルで表示されます。 コンテキスト行の数は n によって設定され、デフォルトは3です。

デフォルトでは、差分制御線(***または---のある線)は末尾に改行を付けて作成されます。 これは、 io.IOBase.readlines()から作成された入力が、 io.IOBase.writelines()での使用に適した差分になるようにするために役立ちます。これは、入力と出力の両方に末尾の改行。

末尾に改行がない入力の場合は、 lineterm 引数を""に設定して、出力が均一に改行されないようにします。

コンテキスト差分形式には通常、ファイル名と変更時刻のヘッダーがあります。 これらのいずれかまたはすべては、 fromfiletofilefromfiledate 、および tofiledate の文字列を使用して指定できます。 変更時間は通常、ISO8601形式で表されます。 指定しない場合、文字列はデフォルトで空白になります。

>>> 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 は、厳密な一致が必要なシーケンス(通常は文字列)であり、 possibleities は、 word と一致するシーケンスのリスト(通常はリスト)です。文字列の)。

オプションの引数 n (デフォルトは3)は、返される近似一致の最大数です。 n0より大きくなければなりません。

オプションの引数 cutoff (デフォルトは0.6)は、[0、1]の範囲の浮動小数点数です。 少なくとも単語と同様のスコアが得られない可能性は無視されます。

可能性の中で最も一致するもの( 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 (文字列のリスト)を比較します。 Different スタイルのデルタ(デルタラインを生成するジェネレーター)を返します。

オプションのキーワードパラメータ linejunk および charjunk は、フィルタリング関数(またはNone)です。

linejunk :単一の文字列引数を受け入れ、文字列がジャンクの場合はtrueを返し、そうでない場合はfalseを返す関数。 デフォルトはNoneです。 モジュールレベルの関数 IS_LINE_JUNK()もあります。この関数は、最大1ポンドの文字('#')を除いて、表示されている文字のない行を除外しますが、基になる SequenceMatcher [ X210X]クラスは、どのラインがノイズを構成するほど頻繁であるかを動的に分析します。これは通常、この関数を使用するよりもうまく機能します。

charjunk :文字(長さ1の文字列)を受け入れ、文字がジャンクの場合は戻り、そうでない場合はfalseを返す関数。 デフォルトはモジュールレベルの関数 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)

デルタを生成した2つのシーケンスの1つを返します。

Different.compare()または ndiff()によって生成されたシーケンスが与えられた場合、ファイル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 (文字列のリスト)を比較します。 統一された差分形式でデルタ(デルタラインを生成するジェネレーター)を返します。

統一された差分は、変更された行と数行のコンテキストだけを表示するコンパクトな方法です。 変更はインラインスタイルで表示されます(前後のブロックを分離するのではなく)。 コンテキスト行の数は n によって設定され、デフォルトは3です。

デフォルトでは、差分制御線(---+++、または@@の線)は末尾に改行を付けて作成されます。 これは、 io.IOBase.readlines()から作成された入力が、 io.IOBase.writelines()での使用に適した差分になるようにするために役立ちます。これは、入力と出力の両方に末尾の改行。

末尾に改行がない入力の場合は、 lineterm 引数を""に設定して、出力が均一に改行されないようにします。

コンテキスト差分形式には通常、ファイル名と変更時刻のヘッダーがあります。 これらのいずれかまたはすべては、 fromfiletofilefromfiledate 、および tofiledate の文字列を使用して指定できます。 変更時間は通常、ISO8601形式で表されます。 指定しない場合、文字列はデフォルトで空白になります。

>>> 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 (バイトオブジェクトのリスト)を比較します。 dfunc によって返される形式のデルタ行(バイトも)のシーケンスを生成します。 dfunc は呼び出し可能である必要があり、通常は unified_diff()または context_diff()のいずれかです。

不明または一貫性のないエンコーディングのデータを比較できます。 n を除くすべての入力は、strではなくbytesオブジェクトである必要があります。 すべての入力( n を除く)を無損失でstrに変換し、dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)を呼び出すことで機能します。 次に、 dfunc の出力がバイトに変換されて戻されるため、受信するデルタラインは、 a および b と同じ不明/一貫性のないエンコーディングになります。

バージョン3.5の新機能。

difflib.IS_LINE_JUNK(line)
無視できる行の場合はTrueを返します。 行 line は、 line が空白であるか、単一の'#'を含む場合、無視できます。それ以外の場合、無視できません。 古いバージョンの ndiff()のパラメータ linejunk のデフォルトとして使用されます。
difflib.IS_CHARACTER_JUNK(ch)
無視できる文字の場合はTrueを返します。 ch がスペースまたはタブの場合、文字 ch は無視できます。それ以外の場合は、無視できません。 ndiff()のパラメータ charjunk のデフォルトとして使用されます。

も参照してください

パターンマッチング:ゲシュタルトアプローチ
JohnWによる同様のアルゴリズムの議論。 ラトクリフとD。 E。 メッツェナー。 これはで公開されました博士 ドブの日記 1988年7月。


SequenceMatcherオブジェクト

SequenceMatcher クラスには、次のコンストラクターがあります。

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

オプションの引数 isjunk は、None(デフォルト)またはシーケンス要素を受け取り、要素が「ジャンク」であり無視する必要がある場合にのみtrueを返す1引数関数である必要があります。 isjunkNoneを渡すことは、lambda x: Falseを渡すことと同じです。 つまり、無視される要素はありません。 たとえば、次のように渡します。

lambda x: x in " \t"

行を文字のシーケンスとして比較していて、空白またはハードタブで同期したくない場合。

オプションの引数 a および b は、比較するシーケンスです。 どちらもデフォルトで空の文字列になります。 両方のシーケンスの要素は、ハッシュ可能である必要があります。

オプションの引数 autojunk を使用して、自動ジャンクヒューリスティックを無効にすることができます。

バージョン3.2の新機能: autojunk パラメーター。

SequenceMatcherオブジェクトは、次の3つのデータ属性を取得します。 bjunk は、 isjunkTrueである b の要素のセットです。 bpopular は、ヒューリスティックによって人気があると見なされる非ジャンク要素のセットです(無効になっていない場合)。 b2j は、 b の残りの要素をそれらが発生する位置のリストにマッピングする辞書です。 bset_seqs()または set_seq2()でリセットされるたびに、3つすべてがリセットされます。

バージョン3.2の新機能: bjunk および bpopular 属性。

SequenceMatcher オブジェクトには次のメソッドがあります。

set_seqs(a, b)

比較する2つのシーケンスを設定します。

SequenceMatcher は、2番目のシーケンスに関する詳細情報を計算してキャッシュするため、1つのシーケンスを複数のシーケンスと比較する場合は、 set_seq2()を使用して一般的に使用されるシーケンスを1回設定し、[X228X ] set_seq1()を繰り返し、他のシーケンスごとに1回ずつ実行します。

set_seq1(a)

比較する最初のシーケンスを設定します。 比較する2番目のシーケンスは変更されません。

set_seq2(b)

比較する2番目のシーケンスを設定します。 比較される最初のシーケンスは変更されません。

find_longest_match(alo, ahi, blo, bhi)

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 が指定された場合、最初に最も長い一致ブロックが上記のように決定されますが、ブロックにジャンク要素が表示されないという追加の制限があります。 次に、そのブロックは、両側の(のみ)ジャンク要素を一致させることによって可能な限り拡張されます。 したがって、結果のブロックがジャンクで一致することはありません。ただし、同一のジャンクがたまたま興味深い一致に隣接している場合を除きます。

これは前と同じ例ですが、空白をジャンクと見なしています。 これにより、' 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)を返します。

このメソッドは、名前付きタプル Match(a, b, size)を返します。

get_matching_blocks()

重複しない一致するサブシーケンスを説明するトリプルのリストを返します。 各トリプルは(i, j, n)の形式であり、a[i:i+n] == b[j:j+n]を意味します。 トリプルは、 ij で単調に増加しています。

最後のトリプルはダミーで、値は(len(a), len(b), 0)です。 n == 0のトリプルはこれだけです。 (i, j, n)(i', j', n')がリスト内の隣接するトリプルであり、2番目がリスト内の最後のトリプルではない場合、i+n < i'またはj+n < j'; 言い換えると、隣接するトリプルは常に隣接していない等しいブロックを表します。

>>> 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があり、残りのタプルには前のタプルの i2 と等しい i1 があり、同様に 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は両方のシーケンスの要素の総数であり、Mは一致の数である場合、これは2.0 * M / Tです。 シーケンスが同一の場合は1.0であり、共通点がない場合は0.0であることに注意してください。

get_matching_blocks()または get_opcodes()がまだ呼び出されていない場合、これは計算にコストがかかります。その場合は、 quick_ratio()または real_quick_ratio()最初に上限を取得します。

ノート

注意: ratio()呼び出しの結果は、引数の順序によって異なる場合があります。 例えば:

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

ratio()の上限を比較的早く返します。

real_quick_ratio()

ratio()の上限を非常にすばやく返します。

quick_ratio()real_quick_ratio()は常に少なくともratio()と同じ大きさですが、文字全体に対する一致の比率を返す3つの方法では、近似レベルが異なるため、異なる結果が得られる可能性があります。 :

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

SequenceMatcherの例

この例では、空白を「ジャンク」と見なして、2つの文字列を比較します。

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

ratio()は、[0、1]にフロートを返し、シーケンスの類似性を測定します。 経験則として、0.6を超えるratio()値は、シーケンスが厳密に一致していることを意味します。

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

も参照してください

  • このモジュールの get_close_matches()関数は、 SequenceMatcher での単純なコード構築を使用して便利な作業を行う方法を示しています。
  • [1] SequenceMatcher で構築された小さなアプリケーション用のシンプルなバージョン管理レシピ。


異なるオブジェクト

Differ で生成されたデルタは、最小 diffであるとは主張しないことに注意してください。 それどころか、最小の差分は、可能な限りどこでも同期し、100ページ離れて偶然に一致することがあるため、直感に反することがよくあります。 同期ポイントを連続する一致に制限すると、局所性の概念が維持されますが、時折、より長い差分が生成されます。

Different クラスには次のコンストラクターがあります。

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

オプションのキーワードパラメーター linejunk および charjunk は、フィルター関数(またはNone)用です。

linejunk :単一の文字列引数を受け入れ、文字列がジャンクの場合はtrueを返す関数。 デフォルトはNoneです。これは、ジャンクと見なされる行がないことを意味します。

charjunk :単一の文字引数(長さ1の文字列)を受け入れ、文字がジャンクの場合はtrueを返す関数。 デフォルトはNoneです。これは、ジャンクと見なされる文字がないことを意味します。

これらのジャンクフィルタリング関数は、マッチングを高速化して違いを見つけ、異なる行や文字が無視されることはありません。 説明については、 find_longest_match()メソッドの isjunk パラメーターの説明をお読みください。

Different オブジェクトは、次の1つの方法で使用されます(デルタが生成されます)。

compare(a, b)

2つの線のシーケンスを比較し、デルタ(線のシーケンス)を生成します。

各シーケンスには、改行で終わる個別の単一行文字列が含まれている必要があります。 このようなシーケンスは、ファイルのようなオブジェクトの readlines()メソッドから取得できます。 生成されたデルタも改行で終了する文字列で構成され、ファイルのようなオブジェクトの writelines()メソッドを介してそのまま印刷できます。


別の例

この例では、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()

Different オブジェクトをインスタンス化するときに、行と文字の「ジャンク」を除外する関数を渡す場合があることに注意してください。 詳細については、 Different()コンストラクターを参照してください。

最後に、2つを比較します。

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

resultは文字列のリストなので、きれいに印刷してみましょう。

>>> 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へのコマンドラインインターフェイス

この例は、difflibを使用してdiffのようなユーティリティを作成する方法を示しています。 Tools/scripts/diff.pyとして、Pythonソースディストリビューションにも含まれています。

#!/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()