difflib — Помощники для вычисления дельт

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


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

class difflib.SequenceMatcher

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

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

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

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

class difflib.Differ

Это класс для сравнения последовательностей строк текста и получения человекочитаемых различий или дельт. 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 (списки строк); верните дельту (generator, порождающую строки дельты) в формате context diff.

Контекстные диффы - это компактный способ показать только те строки, которые изменились, плюс несколько строк контекста. Изменения показываются в стиле «до/после». Количество контекстных строк задается параметром n, который по умолчанию равен трем.

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

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

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

>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> 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: Функция, принимающая один строковый аргумент и возвращающая true, если строка не содержит мусора, или false, если не содержит. По умолчанию используется None. Существует также функция на уровне модуля IS_LINE_JUNK(), которая отфильтровывает строки без видимых символов, за исключением не более одного символа фунта ('#') – однако базовый класс SequenceMatcher делает динамический анализ того, какие строки настолько часты, что представляют собой шум, и это обычно работает лучше, чем использование этой функции.

charjunk: Функция, которая принимает символ (строку длины 1) и возвращает, если символ является нежелательным, или false, если нет. По умолчанию используется функция уровня модуля IS_CHARACTER_JUNK(), которая отфильтровывает пробельные символы (пробел или табуляция; плохой идеей является включение сюда новой строки!)

Tools/scripts/ndiff.py является интерфейсом командной строки для этой функции.

>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
...              'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> print(''.join(diff), end="")
- one
?  ^
+ ore
?  ^
- two
- three
?  -
+ tree
+ emu
difflib.restore(sequence, which)

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

Учитывая последовательность, созданную 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, порождающую строки дельты) в унифицированном формате diff.

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

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

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

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

>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> 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, должны быть байтовыми объектами, а не str. Работает путем преобразования без потерь всех входных данных (кроме n) в str и вызова 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().

См.также

Pattern Matching: The Gestalt Approach

Обсуждение аналогичного алгоритма Джоном В. Рэтклиффом и Д. Е. Метценером. Эта статья была опубликована в журнале Dr. Dobb’s Journal в июле 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 - это сравниваемые последовательности; по умолчанию оба аргумента - пустые строки. Элементы обеих последовательностей должны быть hashable.

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

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

Объекты SequenceMatcher получают три атрибута данных: 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=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 и 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).

Этот метод возвращает 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.

Значения tag являются строками с такими значениями:

Значение

Значение

'replace'

a[i1:i2] следует заменить на b[j1:j2].

'delete'

a[i1:i2] следует удалить. Обратите внимание, что в этом случае j1 == j2.

'insert'

b[j1:j2] должен быть вставлен в a[i1:i1]. Обратите внимание, что i1 == i2 в этом случае.

'equal'

a[i1:i2] == b[j1:j2] (подпоследовательности равны).

Например:

>>> a = "qabxcd"
>>> b = "abycdf"
>>> s = SequenceMatcher(None, a, b)
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
...     print('{:7}   a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(
...         tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))
delete    a[0:1] --> b[0:0]      'q' --> ''
equal     a[1:3] --> b[0:2]     'ab' --> 'ab'
replace   a[3:4] --> b[2:3]      'x' --> 'y'
equal     a[4:6] --> b[3:5]     'cd' --> 'cd'
insert    a[6:6] --> b[5:6]       '' --> 'f'
get_grouped_opcodes(n=3)

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

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

См.также

  • Функция get_close_matches() в этом модуле, которая показывает, как простой код, построенный на SequenceMatcher, может быть использован для выполнения полезной работы.

  • Simple version control recipe для небольшого приложения, построенного с помощью SequenceMatcher.

Различать объекты

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

Класс Differ имеет этот конструктор:

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

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

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

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

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

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

compare(a, b)

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

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

Разница Пример

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