7.4. "difflib" --- 差分の計算を助ける
*************************************

バージョン 2.1 で追加.

このモジュールは、シーケンスを比較するためのクラスや関数を提供していま
す。例えば、ファイルの差分を計算して、それを HTML や context diff,
unified diff などいろいろなフォーマットで出力するために、このモジュー
ルを利用することができます。ディレクトリやファイル群を比較するためには
、 "filecmp" モジュールも参照してください。

class difflib.SequenceMatcher

   柔軟性のあるクラスで、二つのシーケンスの要素は、ハッシュ化できる
   (*hashable*)型である限り何でも比較可能です。基本的なアルゴリズムは
   、1980年代の後半に発表された Ratcliff と Obershelp による"ゲシュタ
   ルトパターンマッチング"と大げさに名づけられたアルゴリズム以前から知
   られている、やや凝ったアルゴリズムです。その考え方は、"junk" 要素を
   含まない最も長い互いに隣接したマッチ列を探すことです (Ratcliff と
   Obershelp のアルゴリズムでは junk を示しません)。このアイデアは、マ
   ッチ列から左または右に伸びる断片に対して再帰的にあてはめられます。
   この方法では編集を最小にする列は取り出されませんが、人間の目からみ
   て「正しい感じ」にマッチする傾向があります。

   **実行時間:** 基本的な Ratcliff-Obershelp アルゴリズムは、最悪の場
   合3乗、期待値で2乗となります。 "SequenceMatcher" オブジェクトでは、
   最悪のケースで2乗、期待値は比較されるシーケンス中に共通に現れる要素
   数に非常にややこしく依存しています。最良の場合は線形時間になります
   。

   **自動 junk ヒューリスティック:** "SequenceMatcher" は、シーケンス
   の特定の要素を自動的に junk として扱うヒューリスティックをサポート
   しています。このヒューリスティックは、各個要素がシーケンス内に何回
   現れるかを数えます。ある要素の重複数が (最初のものは除いて) 合計で
   シーケンスの 1% 以上になり、そのシーケンスが 200 要素以上なら、その
   要素は "popular" であるものとしてマークされ、シーケンスのマッチング
   の目的からは junk として扱われます。このヒューリスティックは、
   "SequenceMatcher" の作成時に "autojunk" パラメタを "False" に設定す
   ることで無効化できます。

   バージョン 2.7.1 で追加: *autojunk* パラメータ。

class difflib.Differ

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

   "Differ" クラスによる差分の各行は、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()" のドキュメントを参照してください。

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

   make_file(fromlines, tolines [, fromdesc][, todesc][, context][, numlines])

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

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

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

   make_table(fromlines, tolines [, fromdesc][, todesc][, context][, numlines])

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

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

   "Tools/scripts/diff.py" はこのクラスへのコマンドラインフロントエン
   ドで、使い方を学ぶ上で格好の例題が入っています。

   バージョン 2.4 で追加.

difflib.context_diff(a, b[, fromfile][, tofile][, fromfiledate][, tofiledate][, n][, lineterm])

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

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

   デフォルトでは、diff の制御行 ("***" や "---" を含む行) の最後には
   、改行文字が付加されます。この場合、入出力とも、行末に改行文字を持
   つので、 "file.readlines()" で得た入力から生成した差分を、
   "file.writelines()" に渡す場合に便利です。

   行末に改行文字を持たない入力に対しては、出力でも改行文字を付加しな
   いように *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']
   >>> for line in context_diff(s1, s2, fromfile='before.py', tofile='after.py'):
   ...     sys.stdout.write(line)  # doctest: +NORMALIZE_WHITESPACE
   *** before.py
   --- after.py
   ***************
   *** 1,4 ****
   ! bacon
   ! eggs
   ! ham
     guido
   --- 1,4 ----
   ! python
   ! eggy
   ! hamster
     guido

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

   バージョン 2.3 で追加.

difflib.get_close_matches(word, possibilities[, n][, cutoff])

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

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

   オプションの引数 *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('apple', keyword.kwlist)
   []
   >>> get_close_matches('accept', keyword.kwlist)
   ['except']

difflib.ndiff(a, b[, linejunk][, charjunk])

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

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

   *linejunk*: 文字列型の引数ひとつを受け取る関数で、文字列が junk な
   らば真を、違うときには偽を返します。 Python 2.3 以降、デフォルトで
   は("None")になります。それまでは、モジュールレべルの関数
   "IS_LINE_JUNK()" であり、それは高々ひとつのシャープ記号("'#'")を除
   いて可視のキャラクタを含まない行をフィルタリングするものです。
   Python 2.3 から、下位にある "SequenceMatcher" クラスが、雑音となる
   くらい頻繁に登場する行であるか否かを、動的に分析します。これは、バ
   ージョン 2.3 以前のデフォルト値よりたいていうまく動作します。

   *charjunk*: 文字(長さ1の文字列)を受け取る関数です。デフォルトでは、
   モジュールレべルの関数 "IS_CHARACTER_JUNK()" であり、これは空白文字
   類 (空白またはタブ、注: 改行文字をこれに含めるのは悪いアイデア！)
   をフィルタリングします。

   "Tools/scripts/ndiff.py" は、この関数のコマンドラインのフロントエン
   ド（インターフェイス）です。

   >>> diff = ndiff('one\ntwo\nthree\n'.splitlines(1),
   ...              'ore\ntree\nemu\n'.splitlines(1))
   >>> print ''.join(diff),
   - one
   ?  ^
   + ore
   ?  ^
   - two
   - three
   ?  -
   + tree
   + emu

difflib.restore(sequence, which)

   差分を生成した元の二つのシーケンスのうち一つを返します。

   "Differ.compare()" または "ndiff()" によって生成された *sequence*
   を与えられると、行頭のプレフィクスを取りのぞいてファイル 1 または 2
   (引数 *which* で指定される) に由来する行を復元します。

   例:

   >>> diff = ndiff('one\ntwo\nthree\n'.splitlines(1),
   ...              'ore\ntree\nemu\n'.splitlines(1))
   >>> diff = list(diff) # materialize the generated delta into a list
   >>> print ''.join(restore(diff, 1)),
   one
   two
   three
   >>> print ''.join(restore(diff, 2)),
   ore
   tree
   emu

difflib.unified_diff(a, b[, fromfile][, tofile][, fromfiledate][, tofiledate][, n][, lineterm])

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

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

   デフォルトでは、diff の制御行 ("---", "+++", "@@" を含む行) は行末
   の改行を含めて生成されます。このようにしてあると、入出力とも行末に
   改行文字を持つので、 "file.readlines()" で得た入力を処理して生成し
   た差分を、 "file.writelines()" に渡す場合に便利です。

   行末に改行文字を持たない入力に対しては、出力でも改行文字を付加しな
   いように *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']
   >>> for line in unified_diff(s1, s2, fromfile='before.py', tofile='after.py'):
   ...     sys.stdout.write(line)   # doctest: +NORMALIZE_WHITESPACE
   --- before.py
   +++ after.py
   @@ -1,4 +1,4 @@
   -bacon
   -eggs
   -ham
   +python
   +eggy
   +hamster
    guido

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

   バージョン 2.3 で追加.

difflib.IS_LINE_JUNK(line)

   無視できる行のとき真を返します。行 *line* は空白、または "'#'" ひと
   つのときに無視できます。それ以外のときには無視できません。 Python
   2.3 以前は "ndiff()" の引数 *linkjunk* にデフォルトで使用されました
   。

difflib.IS_CHARACTER_JUNK(ch)

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

参考:

  Pattern Matching: The Gestalt Approach
     John W. Ratcliff と D. E. Metzener による類似のアルゴリズムに関す
     る議論。Dr. Dobb's Journal 1988年7月号掲載。


7.4.1. SequenceMatcherオブジェクト
==================================

"SequenceMatcher" クラスには、以下のようなコンストラクタがあります:

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

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

      lambda x: x in " \t"

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

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

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

   バージョン 2.7.1 で追加: *autojunk* パラメータ。

   "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, 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* が与えられている場合、上記の通り、はじめに最長のマ
      ッチ列を判定します。ブロック内に 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)" を返します。

      バージョン 2.6 で変更: このメソッドは *named tuple* "Match(a, b,
      size)" を返します。

   get_matching_blocks()

      Return list of triples describing non-overlapping matching
      subsequences. Each triple is of the form "(i, j, n)", and means
      that "a[i:i+n] == b[j:j+n]".  The triples are monotonically
      increasing in *i* and *j*.

      The last triple is a dummy, and has the value "(len(a), len(b),
      0)".  It is the only triple with "n == 0".  If "(i, j, n)" and
      "(i', j', n')" are adjacent triples in the list, and the second
      is not the last triple in the list, then "i+n < i'" or "j+n <
      j'"; in other words, adjacent triples always describe non-
      adjacent equal blocks.

      バージョン 2.5 で変更: 隣接する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 == 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 ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" %
      ...           (tag, i1, i2, a[i1:i2], j1, j2, b[j1:j2]))
       delete a[0:1] (q) b[0:0] ()
        equal a[1:3] (ab) b[0:2] (ab)
      replace a[3:4] (x) b[2:3] (y)
        equal a[4:6] (cd) b[3:5] (cd)
       insert a[6:6] () b[5:6] (f)

   get_grouped_opcodes([n])

      最大 *n* 行までのコンテクストを含むグループを生成するような、ジ
      ェネレータ(*generator*)を返します。

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

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

      バージョン 2.3 で追加.

   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()" の上界を、非常に高速に計算します。

この文字列全体のマッチ率を返す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


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

参考:

  * "SequenceMatcher" を使った、シンプルで使えるコードを知るには、こ
    の モジュールの関数 "get_close_matches()" を参照してください。

  * Simple version control recipe "SequenceMatcher" で作った小規模ア
    プ リケーション。


7.4.3. Differ オブジェクト
==========================

"Differ" オブジェクトによって生成された差分が **最小** であるなどとは
言いません。むしろ、最小の差分はしばしば直観に反しています。その理由は
、どこでもできるとなれば一致を見いだしてしまうからで、ときには思いがけ
なく100ページも離れたマッチになってしまうのです。一致点を互いに隣接し
たマッチに制限することで、場合によって長めの差分を出力するというコスト
を掛けることにはなっても、ある種の局所性を保つことができるのです。

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

class difflib.Differ([linejunk[, charjunk]])

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

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

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

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

   compare(a, b)

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

      各シーケンスの要素は、改行で終わる独立した単一行からなる文字列で
      なければなりません。そのようなシーケンスは、ファイル風オブジェク
      トの "readlines()" メソッドによって得ることができます。（得られ
      る）差分は改行文字で終了する文字列のシーケンスとして得られ、ファ
      イル風オブジェクトの "writelines()" メソッドによって出力できる形
      になっています。


7.4.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(1)
>>> 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(1)

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


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

この例は、 difflib を使って "diff" に似たユーティリティーを作成する方
法を示します。これは、 Python のソース配布物にも、
"Tools/scripts/diff.py" として含まれています。

   """ 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, time, difflib, optparse

   def main():
        # Configure the option parser
       usage = "usage: %prog [options] fromfile tofile"
       parser = optparse.OptionParser(usage)
       parser.add_option("-c", action="store_true", default=False,
                         help='Produce a context format diff (default)')
       parser.add_option("-u", action="store_true", default=False,
                         help='Produce a unified format diff')
       hlp = 'Produce HTML side by side diff (can use -c and -l in conjunction)'
       parser.add_option("-m", action="store_true", default=False, help=hlp)
       parser.add_option("-n", action="store_true", default=False,
                         help='Produce a ndiff format diff')
       parser.add_option("-l", "--lines", type="int", default=3,
                         help='Set number of context lines (default 3)')
       (options, args) = parser.parse_args()

       if len(args) == 0:
           parser.print_help()
           sys.exit(1)
       if len(args) != 2:
           parser.error("need to specify both a fromfile and tofile")

       n = options.lines
       fromfile, tofile = args # as specified in the usage string

       # we're passing these as arguments to the diff function
       fromdate = time.ctime(os.stat(fromfile).st_mtime)
       todate = time.ctime(os.stat(tofile).st_mtime)
       with open(fromfile, 'U') as f:
           fromlines = f.readlines()
       with open(tofile, 'U') as f:
           tolines = f.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)

       # we're using writelines because diff is a generator
       sys.stdout.writelines(diff)

   if __name__ == '__main__':
       main()
