difflib — Helpers for computing deltas

Вихідний код: 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 починається двобуквеним кодом:

Код

Значення

'- ''

рядок, унікальний для послідовності 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)

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

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

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

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

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

compare(a, b)

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

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(), щоб дізнатися про значення аргументів за замовчуванням та описи.

Публічними є такі методи:

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.

Junk definition functions

difflib.IS_LINE_JUNK(line)

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

difflib.IS_CHARACTER_JUNK(ch)

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

SequenceMatcher objects

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(), щоб отримати верхня межа.

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

Examples

SequenceMatcher examples

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

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