# `difflib` — Helpers for computing deltas

Вихідний код: Lib/difflib.py

Цей модуль містить класи та функції для порівняння послідовностей. Він може бути використаний, наприклад, для порівняння файлів і може видавати інформацію про відмінності файлів у різних форматах, включаючи HTML, контекст і уніфіковані відмінності. Для порівняння каталогів і файлів див. також модуль `filecmp`.

class difflib.SequenceMatcher

  Це гнучкий клас для порівняння пар послідовностей будь-якого типу, за умови, що елементи послідовності hashable. Базовий алгоритм передує алгоритму, опублікованому наприкінці 1980-х років Реткліфом і Обершелпом під гіперболічною назвою «відповідність гештальт-шаблону», і є трохи моднішим за нього. Ідея полягає в тому, щоб знайти найдовшу безперервну відповідну підпослідовність, яка не містить «сміттєвих» елементів; ці «сміттєві» елементи є такими, що в певному сенсі нецікаві, наприклад порожні рядки чи пробіли. (Обробка сміття є розширенням алгоритму Раткліффа та Обершелпа.) Ця сама ідея потім рекурсивно застосовується до фрагментів послідовностей ліворуч і праворуч від відповідної підпослідовності. Це не дає мінімальних послідовностей редагування, але, як правило, дає збіги, які «виглядають правильно» для людей.

  Час: основний алгоритм Реткліффа-Обершелпа – це кубічний час у найгіршому випадку та квадратичний час у очікуваному випадку. `SequenceMatcher` — це квадратичний час для найгіршого випадку та має очікувану поведінку, складно залежну від кількості спільних елементів послідовностей; найкращий час – лінійний.

  Автоматична евристика сміття: `SequenceMatcher` підтримує евристику, яка автоматично розглядає певні елементи послідовності як сміття. Евристика підраховує, скільки разів кожен окремий елемент з’являється в послідовності. Якщо дублікати елемента (після першого) становлять більше 1% послідовності, а послідовність складається щонайменше з 200 елементів, цей елемент позначається як «популярний» і розглядається як небажаний для цілей відповідності послідовності. Цю евристику можна вимкнути, встановивши для аргументу `autojunk` значення `False` під час створення `SequenceMatcher`.

  Змінено в версії 3.2: Added the autojunk parameter.

class difflib.Differ

  Це клас для порівняння послідовностей рядків тексту та створення зрозумілих людині відмінностей або дельт. Differ використовує `SequenceMatcher` як для порівняння послідовностей рядків, так і для порівняння послідовностей символів у подібних (майже відповідних) рядках.

  Кожен рядок дельти `Differ` починається двобуквеним кодом:

  Код | Значення
  --- | ---
  `'- ''` | рядок, унікальний для послідовності 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.

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

    Порівнює fromlines і tolines (списки рядків) і повертає рядок, який є повним HTML-файлом, що містить таблицю, у якій показано рядкові відмінності з виділеними міжрядковими та внутрішньорядковими змінами.

    fromdesc і todesc є необов’язковими аргументами ключових слів для вказівки рядків заголовків стовпців файлу from/to (обидва за замовчуванням є порожнім рядком).

    context і numlines є необов’язковими аргументами ключового слова. Встановіть для context значення `True`, якщо мають відображатися контекстуальні відмінності, інакше значенням за замовчуванням є `False` для відображення повних файлів. numlines за умовчанням має значення `5`. Якщо context має значення `True`, numlines контролює кількість рядків контексту, які оточують виділення різниці. Коли context має значення `False`, numlines контролює кількість рядків, які відображаються перед підсвічуванням різниці під час використання «наступних» гіперпосилань (встановлення нуля призведе до того, що «наступні» гіперпосилання розмістять наступне підсвічування різниці в у верхній частині браузера без будь-якого початкового контексту).

    Примітка

    fromdesc і todesc інтерпретуються як неекранований HTML і мають бути належним чином екрановані під час отримання вхідних даних із ненадійних джерел.

    Змінено в версії 3.5: Додано charset лише ключовий аргумент. Стандартний набір кодів HTML-документа змінено з `'ISO-8859-1'` на `'utf-8'`.

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

    Порівнює fromlines і tolines (списки рядків) і повертає рядок, який є повною HTML-таблицею, що показує рядкові відмінності з виділеними міжрядковими та внутрішньорядковими змінами.

    Аргументи для цього методу такі самі, як і для методу `make_file()`.

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

  Порівняти a і b (списки рядків); повертає дельту (generator, що генерує дельта-рядки) у форматі контекстної різниці.

  Контекстні відмінності — це компактний спосіб показати лише рядки, які змінилися, плюс кілька рядків контексту. Зміни відображаються у стилі до/після. Кількість рядків контексту встановлюється n, яке за замовчуванням дорівнює трьом.

  За замовчуванням рядки керування відмінностями (ті, що мають `***` або `---`) створюються з кінцевим символом нового рядка. Це корисно, щоб вхідні дані, створені з `io.IOBase.readlines()`, призводили до відмінностей, які придатні для використання з `io.IOBase.writelines()`, оскільки і вхідні, і вихідні дані мають кінцеві символи нового рядка.

  Для вхідних даних, які не мають кінцевих символів нового рядка, встановіть для аргументу 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)

  Поверніть список найкращих «досить хороших» збігів. слово — це послідовність, для якої потрібні близькі збіги (зазвичай це рядок), а можливості — це список послідовностей, з якими потрібно зіставити слово (зазвичай список рядків).

  Необов’язковий аргумент n (за замовчуванням `3`) — це максимальна кількість близьких збігів для повернення; n має бути більше, ніж `0`.

  Необов’язковий аргумент 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)

  Порівняти a і b (списки рядків); повертає `Differ`-стиль дельти (generator, який генерує дельта-лінії).

  Додаткові параметри ключових слів linejunk і charjunk є функціями фільтрації (або `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) і повертає, якщо символ непотрібний, або false, якщо ні. За замовчуванням використовується функція рівня модуля `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()`, витягти рядки, що походять із файлу 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, що генерує дельта-лінії) в уніфікованому форматі різниці.

  Уніфіковані відмінності — це компактний спосіб показати лише рядки, які змінилися, плюс кілька рядків контексту. Зміни відображаються у вбудованому стилі (замість окремих блоків до/після). Кількість рядків контексту встановлюється n, яке за замовчуванням дорівнює трьом.

  За замовчуванням рядки керування відмінностями (з `---`, `+++` або `@@`) створюються з кінцевим символом нового рядка. Це корисно, щоб вхідні дані, створені з `io.IOBase.readlines()`, призводили до відмінностей, які придатні для використання з `io.IOBase.writelines()`, оскільки і вхідні, і вихідні дані мають кінцеві символи нового рядка.

  Для вхідних даних, які не мають кінцевих символів нового рядка, встановіть для аргументу 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')

  Порівняти a і b (списки об’єктів bytes) за допомогою dfunc; дає послідовність дельта-рядків (також байтів) у форматі, який повертає dfunc. dfunc має бути викликом, як правило, `unified_diff()` або `context_diff()`.

  Дозволяє порівнювати дані з невідомим або суперечливим кодуванням. Усі вхідні дані, крім n, мають бути об’єктами bytes, а не str. Працює без втрат, перетворюючи всі вхідні дані (крім n) на str і викликаючи `dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)`. Вихідні дані dfunc потім перетворюються назад у байти, тому дельта-рядки, які ви отримуєте, мають таке ж невідоме/непослідовне кодування, що й a і b.

  Added in version 3.5.

difflib.IS_LINE_JUNK(line)

  Повертає `True` для ігнорованих рядків. Рядок рядок ігнорується, якщо рядок порожній або містить один `'#'`, інакше він не ігнорується. Використовується за замовчуванням для параметра linejunk у `ndiff()` у старих версіях.

difflib.IS_CHARACTER_JUNK(ch)

  Повертає `True` для ігнорованих символів. Символ ch ігнорується, якщо ch є пробілом або табуляцією, інакше він не ігнорується. Використовується за замовчуванням для параметра charjunk у `ndiff()`.

Дивись також

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.

## Об’єкти SequenceMatcher

Клас `SequenceMatcher` має такий конструктор:

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

  Необов’язковий аргумент isjunk має бути `None` (за замовчуванням) або функція з одним аргументом, яка приймає елемент послідовності та повертає true тоді і тільки тоді, коли елемент є «сміттєвим» і його слід ігнорувати. Передача `None` для isjunk еквівалентна передачі `lambda x: False`; іншими словами, жоден елемент не ігнорується. Наприклад, pass:

      lambda x: x in " \t"

  якщо ви порівнюєте рядки як послідовності символів і не хочете синхронізувати пробіли чи жорсткі табуляції.

  Необов’язкові аргументи a і b є послідовностями для порівняння; обидва за замовчуванням порожні рядки. Елементи обох послідовностей мають бути hashable.

  Необов’язковий аргумент autojunk можна використовувати для вимкнення автоматичної евристики сміття.

  Змінено в версії 3.2: Added the autojunk parameter.

  Об’єкти SequenceMatcher отримують три атрибути даних: bjunk — це набір елементів b, для яких isjunk має значення `True`; bpopular — це набір непотрібних елементів, які вважаються популярними за евристикою (якщо її не вимкнено); b2j — це dict, що відображає решту елементів b на список позицій, де вони зустрічаються. Усі три скидаються щоразу, коли b скидається за допомогою `set_seqs()` або `set_seq2()`.

  Added in version 3.2: Атрибути bjunk і bpopular.

  Об’єкти `SequenceMatcher` мають такі методи:

  set_seqs(a, b)

    Встановіть дві послідовності для порівняння.

  `SequenceMatcher` обчислює та кешує детальну інформацію про другу послідовність, тому, якщо ви хочете порівняти одну послідовність із багатьма послідовностями, використовуйте `set_seq2()`, щоб один раз установити типову послідовність і викликати `set_seq1()` повторно, один раз для кожної з інших послідовностей.

  set_seq1(a)

    Встановіть першу послідовність для порівняння. Друга послідовність для порівняння не змінюється.

  set_seq2(b)

    Встановіть другу послідовність для порівняння. Перша послідовність для порівняння не змінюється.

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

    Знайти найдовший відповідний блок у `a[alo:ahi]` і `b[blo:bhi]`.

    Якщо isjunk пропущено або `None`, `find_longest_match()` повертає `(i, j, k)` так, що `a[i:i+k]` дорівнює `b[j:j+k]`, де `alo <= i <= i+k <= ahi` and `blo <= j <= j+k <= bhi`. For all `(i', j', k')` meeting those conditions, the additional conditions `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'` з `' abcd'` у кінці другої послідовності. Замість цього може збігатися лише `'abcd'` і збігається з крайнім лівим `'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()

    Повернути список трійок, що описують відповідні підпослідовності, що не перекриваються. Кожна трійка має форму `(i, j, n)` і означає, що `a[i:i+n] == b[j:j+n]`. Трійки монотонно зростають у i та j.

    Остання трійка є фіктивною і має значення `(len(a), len(b), 0)`. Це єдина трійка з `n == 0`. Якщо `(i, j, n)` і `(i', j', n')` є суміжними трійками в списку, а друга не є останньою трійкою в списку, тоді `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()

    Повернути список із 5-ти кортежів, які описують, як перетворити a на b. Кожен кортеж має форму `(tag, i1, i2, j1, j2)`. Перший кортеж має `i1 == j1 == 0`, а решта кортежів мають i1, що дорівнює i2 з попереднього кортежу, і, так само, j1 дорівнює попередньому j2.

    Значення тегу є рядками з такими значеннями:

    Значення | Значення
    --- | ---
    `'замінити'` | `a[i1:i2]` слід замінити на `b[j1:j2]`.
    `'видалити'` | `a[i1:i2]` слід видалити. Зверніть увагу, що в цьому випадку `j1 == j2`.
    `'вставити'` | `b[j1:j2]` слід вставити в `a[i1:i1]`. Зверніть увагу, що в цьому випадку `i1 == i2`.
    `'рівний''` | `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)

    Повертає generator груп із до 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()`.

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

У цьому прикладі порівнюються два рядки, вважаючи пробіли «сміттєвими»:

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

Дивись також

- Функція `get_close_matches()` у цьому модулі, яка показує, як простий код, створений на `SequenceMatcher`, можна використовувати для виконання корисної роботи.

- Simple version control recipe for a small application built with `SequenceMatcher`.

## Різні об’єкти

Зауважте, що `Differ`-генеровані дельти не претендують на мінімальні відмінності. Навпаки, мінімальні відмінності часто суперечать інтуїції, оскільки вони синхронізуються будь-де, де можливо, іноді випадково збігаються на 100 сторінках одна від одної. Обмеження точок синхронізації суміжними збігами зберігає деяке уявлення про локальність за рахунок випадкових витрат на створення довшої різниці.

Клас `Differ` має такий конструктор:

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

  Додаткові параметри ключових слів linejunk і charjunk призначені для функцій фільтра (або `None`):

  linejunk: функція, яка приймає один рядковий аргумент і повертає істину, якщо рядок є небажаним. Типовим значенням є `None`, що означає, що жоден рядок не вважається небажаним.

  charjunk: функція, яка приймає односимвольний аргумент (рядок довжиною 1) і повертає істину, якщо символ непотрібний. Типовим значенням є `None`, що означає, що жоден символ не вважається небажаним.

  Ці функції фільтрації сміття пришвидшують зіставлення для пошуку відмінностей і не призводять до ігнорування будь-яких відмінних рядків чи символів. Щоб отримати пояснення, прочитайте опис параметра isjunk методу `find_longest_match()`.

  Об’єкти `Differ` використовуються (генеруються дельти) за допомогою одного методу:

  compare(a, b)

    Порівняйте дві послідовності рядків і згенеруйте дельту (послідовність рядків).

    Кожна послідовність повинна містити окремі однорядкові рядки, що закінчуються символом нового рядка. Такі послідовності можна отримати з методу файлоподібних об’єктів `readlines()`. Згенерована дельта також складається з рядків із закінченням нового рядка, готових до друку як є за допомогою методу `writelines()` файлоподібного об’єкта.

## Різний приклад

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` ми можемо передати функції для фільтрації «сміття» рядків і символів. Подробиці див. у конструкторі `Differ()`.

Нарешті, ми порівнюємо два:

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

`результат` - це список рядків, тож давайте красиво надрукуємо його:

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