difflib — Хэлперы для вычисления различий

Исходный код: Lib/difflib.py


Этот модуль предоставляет классы и функции для сравнения последовательностей. Он может быть использован для сравнения файлов, и может создавать разностную информацию в различных форматах, включая HTML, контекста и унифицированные diff’ы. Для сравнения каталогов и файлов см. также модуль filecmp.

class difflib.SequenceMatcher

Это гибкий класс для сравнения пар последовательностей любого типа при условии, что элементы последовательности являются хэшируемым. Базовый алгоритм предшествует, и является немного более модным, чем алгоритм, опубликованный в конце 1980-х годов Ratcliff и Obershelp под гиперболическим названием «сопоставление гештальт-паттернов». Идея состоит в том, чтобы найти самую длинную смежную подпоследовательность, которая не содержит «нежелательных» элементов; эти «нежелательные» элементы являются неинтересными в некотором смысле, например пустые строки или пробелы. (Обработка нежелательной почты является расширением алгоритма Ratcliff и Obershelp.) Эта же идея затем рекурсивно применяется к частям последовательностей слева и справа от совпадающей подпоследовательности. Это не дает минимального редактирования последовательности, но как правило, возвращают совпадения, которые «выглядят правильно» для людей.

Тайминг: базовым алгоритмом Ratcliff-Obershelp работает за кубическое время в наихудшем случае и квадратичное время в ожидаемом случае. SequenceMatcher работает за квадратичное время для наихудшего случая и поведение ожидаемого случая, зависит от сложного способа определения количества элементов содержащихся в последовательности; лучшее время случая линейно.

Автоматическая мусорная эвристика: SequenceMatcher поддерживает эвристику, которая автоматически рассматривает определенные элементы последовательности как нежелательные. Эвристика подсчитывает, сколько раз каждый отдельный элемент появляется в последовательности. Если дубликаты элемента (после первого) составляют более 1% последовательности, а длина последовательности составляет не менее 200 элементов, этот элемент помечается как «популярный» и рассматривается как нежелательный для целей сопоставления последовательности. Этот эвристический параметр можно отключить, установив для аргумента autojunk значение False при создании SequenceMatcher.

Добавлено в версии 3.2: Параметр autojunk.

class difflib.Differ

Класс для сравнения последовательностей строк текста и получения человекочитаемых различий или дельт. Отличие от использования SequenceMatcher заключается в сравнении последовательности строк, так и сравнении последовательности символов в аналогичных (почти совпадающих) строках.

Каждая строка дельты Differ начинается с двухбуквенного кода:

Код Смысл
'- ' строка, уникальная для последовательности 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')

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

fromdesc и todesc необязательные ключевые аргументами для указания заголовка столбца из/в файл строки (по умолчанию для пустая строка).

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

Примечание

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

Tools/scripts/diff.py является фронтендом командной строки для этого класса и содержит хороший пример его использования.

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

Сравнение a и b (списки строк); возвращает дельту (генератор, производящий дельты строк) в diff формате контекста.

Контекстные diff’ы являются компактным способом отображения отличий строк плюс несколько строк контекста. Изменения отображаются в стиле до/после. Число строк контекста задается значением n, которое по умолчанию равно трем.

По умолчанию управляющие строки diff (с *** или ---) создаются с завершающей новой строкой. Это полезно для того, чтобы входные данные, созданные на основе io.IOBase.readlines(), приводили к различиям, которые подходят для использования с io.IOBase.writelines(), поскольку и входные, и выходные данные содержат завершающие новые строки.

Для входов, не содержащих завершающие новые строки, задайте для аргумента lineterm значение "", чтобы выходные данные были равномерно свободны от новых строк.

У diff формата контекста обычно есть заголовок для времени модификации и имен файлов. Любое или все из них могут быть определены с использованием строк для fromfile, tofile, fromfiledate и tofiledate. Время модификации обычно выражается в формате ISO 8601. Если не указано, по умолчанию строки пустые.

>>> 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 - последовательность, для которой требуются близкие совпадения (обычно строка), и possibilities - список последовательностей, с которыми следует сопоставлять word (обычно список строк).

Необязательный аргумент n (по умолчанию 3) - максимальное число совпадений закрытия для возврата; n должно быть больше 0.

Дополнительный аргумент cutoff (по умолчанию 0.6) является float в диапазоне [0, 1]. Возможности, которые не оцениваются хотя бы так, как word, игнорируются.

Лучшие (не более 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 стиль дельты (генератор, генератор дельта-строк).

Дополнительные ключевые параметры linejunk и charjunk фильтрующие функции (или None):

linejunk: функция, принимающая один строковый аргумент и возвращающая значение True, если строка нежелательна, или False, если нет. Значение по умолчанию равно None. Существует также функция уровня модуля IS_LINE_JUNK(), которая отфильтровывает строки без видимых символов, за исключением максимум одного символа фунта ('#') - однако лежащий в основе класс SequenceMatcher делает динамический анализ того, какие строки так часты, чтобы составлять шум, и это обычно работает лучше, чем использование этой функции.

charjunk: функция, принимающая символ (строка длиной 1) и возвращающая True, если символ нежелательный, или 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)

Возвращает одну из двух последовательностей, которые создали дельту.

При наличии sequence, производимого 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 (списки строк); возвращает дельту (генератор, генерирующий дельта-строки) в унифицированном формате diff.

Унифицированный diff - это компактный способ отображения только тех строк, которые изменились, плюс несколько строк контекста. Изменения отображаются в встроенном стиле (вместо разделения блоков до/после). Число строк контекста задается значением n, которое по умолчанию равно трем.

По умолчанию управляющие строки diff (с ---, +++ или @@) создаются с символом завершающей новой строки. Это полезно для того, чтобы входные данные, созданные на основе io.IOBase.readlines(), приводили к различиям, которые подходят для использования с io.IOBase.writelines(), поскольку и входные, и выходные данные содержат завершающие символы новых строк.

Для входов, не содержащих символов завершающих новых строк, задайте для аргумента lineterm значение "", чтобы выходные данные были равномерно свободны от новых строк.

У diff формата контекста обычно есть заголовок для времени модификации и имён файлов. Любое или все из них могут быть определены с использованием строк для fromfile, tofile, fromfiledate и tofiledate. Время модификации обычно выражается в формате ISO 8601. Если не определено, строки по умолчанию пусты.

>>> 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 (списки объектов байтов) с помощью dfunc; возвращая последовательность дельта-строк (также байт) в формате возвращаемым dfunc. dfunc должен быть вызываемым, как правило, unified_diff() или context_diff().

Позволяет вам сравнивать данные с неизвестным или непоследовательными кодировками. Все входные данные, за исключением n, должны быть байтовыми объектами, а не строкой. Работает путем преобразования без потерь всех входных данных (кроме n) в строку и вызова dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm). Затем выходные данные dfunc преобразуются обратно в байты, поэтому полученные дельта-строки имеют те же неизвестные/несогласованные кодировки, что и a и b.

Добавлено в версии 3.5.

difflib.IS_LINE_JUNK(line)

Возвращает True для невежественных строк. Строка line игнорируется, если line пуста или содержит единственный '#', иначе она не игнорируется. Используется по умолчанию для параметра linejunk в ndiff() в более ранних версиях.

difflib.IS_CHARACTER_JUNK(ch)

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

См.также

Сопоставление Паттернов: Гештальт-Подход
Обсуждение аналогичного алгоритма John W. Ratcliff и D. E. Metzener. Он был опубликован в Dr. Dobb’s журнале в июле 1988 года.

Объекты SequenceMatcher

Класс SequenceMatcher содержит конструктор:

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

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

lambda x: x in " \t"

если вы сравниваете строки как последовательности символов и не хотите синхронизироваться на пустотах или жестких табах.

Необязательные аргументы a и b являются последовательностями для сравнения; оба значения по умолчанию для пустых строк. Элементы обеих последовательностей должны быть хэшируемы.

Дополнительным аргументом autojunk может быть используем для отключения автоматического эвристического мусора.

Добавлено в версии 3.2: Параметр autojunk.

Объекты RequestMatcher получают три атрибута данных: bjunk - набор элементов b, для которых isjunk является True; bpopular - набор не-мусорных элементов, считающихся популярными эвристическим (если не отключен); b2j - это словарь, отображающий остальные элементы b в список позиций, где они находятся. Все три элемента сбрасываются всякий раз, когда b сбрасывается с помощью set_seqs() или set_seq2().

Добавлено в версии 3.2: bjunk и bpopular атрибуты.

Объекты SequenceMatcher содержат следующие методы:

set_seqs(a, b)

Задать две сравниваемые последовательности.

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

set_seq1(a)

Установить первую последовательность для сравнения. Вторая последовательность для сравнения не изменяется.

set_seq2(b)

установить вторую последовательность для сравнения. Первая последовательность для сравнения не изменяется.

find_longest_match(alo, ahi, blo, bhi)

Найти самый длинный соответствующий блок в 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 и 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' соответствовал ' 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).

Этот метод возвращает именованный кортеж Match(a, b, size).

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.

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] (суб-последовательности равны).

Например:

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

Возвращает меру сходства последовательностей в виде float в диапазоне [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():

>>> 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() возвращает float в [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.

Чтобы узнать, как изменить первую последовательность во вторую, используйте команду 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

Обратите внимание, что дельты, созданные в Differ, не претендуют на то, чтобы быть минимальными diff’ами. Наоборот, минимальные расхождения часто нелогичный, потому что они синхронизируются где угодно, иногда случайно совпадают на 100 страниц друг с другом. Ограничение синхронизирующих точек смежными совпадениями сохраняет некоторое представление о местоположении при случайных затратах на производство более длинных различий.

Класс Differ содержит конструктор:

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

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

linejunk: функция, принимающая один строковый аргумент и возвращающая значение true, если строка является нежелательной. Значение по умолчанию равно None, что означает, что ни одна строка не считается нежелательной.

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

Эти функции нежелательной фильтрации ускоряют сопоставление для поиска различий и не приводят к игнорированию различных строк или символов. Прочитайте описание параметра метода find_longest_match() isjunk для объяснения.

Differ объекты используются (создаются дельты) одним методом:

compare(a, b)

Сравнить две последовательности строк и создать дельту (последовательность строк).

Каждая последовательность должна содержать отдельную одну строку, заканчивающуюся символом новой строки. Такие последовательности могут быть получены из метода readlines() файлоподобных объектов. Сформированная дельта также состоит из завершенных симолом новой строки строк, готовых к печати как есть с помощью метода writelines() файлоподобного объекта.

Пример Differ

В этом примере сравниваются два текста. Сначала мы настраиваем тексты, последовательностью отдельных однострочных строк, заканчивающихся символом новой строки (такие последовательности также могут быть получены из метода 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()

Обратите внимание, что при создании экземпляра объекта Differ мы можем передать функции для фильтрации строки и «нежелательный» символ. Дополнительные сведения см. в конструкторе Differ().

Наконец, мы сравниваем два:

>>> 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. Она также содержится в дистрибутиве исходников Python, как Tools/scripts/diff.py.

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