difflib — Вспомогательные средства для вычисления различий
Исходный код: Lib/difflib.py
Этот модуль предоставляет классы и функции для сравнения последовательностей. Большинство из них сравнивают последовательности текстовых строк (например, списки строк или файловые объекты) и создают различия — отчёты о найденных отличиях. Различия можно представлять в различных форматах, в том числе HTML, а также в контекстном и унифицированном форматах — таких, которые создают инструменты вроде diff и git diff.
Сравнение выполняется с помощью алгоритма сопоставления, реализованного в SequenceMatcher — гибком классе для сравнения пар последовательностей любого типа, не только текста, при условии, что элементы последовательности являются хешируемыми.
Эвристика для мусорных элементов
difflib использует эвристику для определения мусорных элементов: некоторые элементы считаются мусорными и игнорируются при поиске совпадений. В идеале это не представляющие интереса или часто встречающиеся элементы, такие как пустые строки или пробельные символы.
Эта эвристика может ускорить алгоритм (поскольку сокращает число возможных комбинаций) и сделать результаты более понятными для людей (обычно разделяя текст по пробельным символам). Но она также может привести к патологическим случаям:
- Неправильно выбранные мусорные элементы могут привести к неожиданно большому (но всё ещё корректному) результату.
- Эвристика по умолчанию несимметрична: при определении мусорных элементов проверяется только вторая последовательность, поэтому сравнение A с B может дать результат, отличающийся от результата сравнения B с A с последующей инверсией.
По умолчанию, если вторая входная последовательность содержит не менее 200 элементов, элементы, составляющие более 1% от её длины, считаются мусорными.
В зависимости от ваших данных имеет смысл отключить эту эвристику (установив аргумент autojunk метода SequenceMatcher в значение False) или настроить её (используя аргумент isjunk, возможно, одну из предопределённых функций).
Алгоритм difflib
Алгоритм, используемый в SequenceMatcher, появился раньше алгоритма, опубликованного в конце 1980-х годов Рэтклиффом и Обершелпом под гиперболическим названием «гештальтное сопоставление образов», и несколько сложнее его. Идея заключается в том, чтобы найти самую длинную непрерывную подпоследовательность, общую для обоих входных данных, а затем рекурсивно обработать части последовательностей слева и справа от совпавшей подпоследовательности.
См. также
- Сопоставление образов: гештальтный подход
-
Обсуждение похожего алгоритма Джона У. Рэтклиффа и Д. Э. Меценера. Статья была опубликована в журнале Dr. Dobb’s Journal в июле 1988 года.
В качестве расширения алгоритма Рэтклиффа и Обершелпа difflib ищет самую длинную непрерывную подпоследовательность, не содержащую мусорных элементов. Подробности см. в разделе Эвристика для мусорных элементов.
Особенность реализации CPython: временная сложность
Базовый алгоритм Рэтклиффа — Обершелпа в худшем случае имеет кубическую временную сложность, а в среднем — квадратичную. Алгоритм difflib имеет квадратичную временную сложность в худшем случае, а его поведение в среднем зависит сложным образом от количества общих элементов последовательностей; в лучшем случае временная сложность линейная.
Формирование различий
-
class difflib.Differ -
Этот класс предназначен для сравнения последовательностей текстовых строк и создания понятных человеку различий или дельт. Класс Differ использует
SequenceMatcherкак для сравнения последовательностей строк, так и для сравнения последовательностей символов внутри похожих (почти совпадающих) строк.Каждая строка дельты
Differначинается с двухбуквенного кода:Код
Значение
'- 'строка встречается только в последовательности 1
'+ 'строка встречается только в последовательности 2
' 'строка встречается в обеих последовательностях
'? 'строка отсутствует в обеих входных последовательностях
Строки, начинающиеся с «
?», помогают заметить различия внутри строк и отсутствуют в обеих входных последовательностях. Такие строки могут сбивать с толку, если последовательности содержат пробельные символы, например пробелы, табуляции или символы перевода строки.Обратите внимание, что дельты, созданные
Differ, не претендуют на то, чтобы быть минимальными. Напротив, минимальные различия часто неинтуитивны для человека, поскольку они синхронизируются при любой возможности, иногда на случайных совпадениях, отстоящих друг от друга на 100 страниц. Ограничение точек синхронизации непрерывными совпадениями сохраняет некоторое представление о локальности, но иногда приводит к более длинным различиям.У класса
Differесть следующий конструктор:-
__init__(linejunk=None, charjunk=None) -
Необязательные именованные параметры linejunk и charjunk предназначены для функций фильтрации (или
None):linejunk: функция, принимающая один строковый аргумент и возвращающая истину, если строка является мусорной. По умолчанию используется
None, то есть ни одна строка не считается мусорной.charjunk: функция, принимающая один символьный аргумент (строку длины 1) и возвращающая истину, если символ является мусорным. По умолчанию используется
None, то есть ни один символ не считается мусорным.Эти функции фильтрации мусорных элементов ускоряют поиск различий и не приводят к игнорированию отличающихся строк или символов. Объяснение см. в описании параметра isjunk метода
find_longest_match().
Объекты
Differиспользуются (дельты создаются) с помощью одного метода:-
compare(a, b) -
Сравнивает две последовательности строк и создаёт дельту (последовательность строк).
Каждая последовательность должна содержать отдельные однострочные строки, заканчивающиеся символами перевода строки. Такие последовательности можно получить с помощью метода
readlines()файловоподобных объектов. Созданная дельта также состоит из строк, заканчивающихся символами перевода строки, и её можно вывести без изменений с помощью методаwritelines()файловоподобного объекта.
-
-
class difflib.HtmlDiff -
Этот класс можно использовать для создания HTML-таблицы (или полного HTML-файла, содержащего таблицу), в которой текст сравнивается построчно рядом друг с другом, а изменения между строками и внутри строк выделяются. Таблицу можно создать в режиме полного или контекстного отображения различий.
Предупреждение
Перед сравнением конечные символы перевода строки удаляются, поэтому результат может быть неполным. Подробности см. в gh-71896.
Конструктор этого класса:
-
__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 задаёт количество строк перед выделенным различием, показываемых при переходе по гиперссылкам «следующее» (если установить значение 0, гиперссылки «следующее» будут размещать следующее выделенное различие вверху окна браузера, без начального контекста).Примечание
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 (списки строк) и возвращает дельту ( генератор, выдающий строки дельты) в контекстном формате различий.
Контекстные различия — это компактный способ показать только изменённые строки и несколько окружающих их контекстных строк. Изменения отображаются в формате «до/после». Количество контекстных строк задаётся параметром 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) -
Возвращает список наиболее подходящих совпадений, достаточно близких к искомому. word — последовательность, для которой требуется найти близкие совпадения (обычно строка), а possibilities — список последовательностей, среди которых ищутся совпадения с word (обычно список строк).
Необязательный аргумент n (по умолчанию
3) задаёт максимальное количество возвращаемых близких совпадений; значение n должно быть больше0.Необязательный аргумент cutoff (по умолчанию
0.6) — число с плавающей точкой в диапазоне [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: функция, принимающая один строковый аргумент и возвращающая истину, если строка является мусорной, или ложь в противном случае. По умолчанию используется
None. Также на уровне модуля доступна функцияIS_LINE_JUNK(), которая отфильтровывает строки без видимых символов, кроме не более чем одного символа решётки ('#'). Однако базовый классSequenceMatcherдинамически определяет, какие строки встречаются настолько часто, что становятся шумом; обычно этот подход работает лучше, чем использование данной функции.charjunk: функция, принимающая символ (строку длины 1) и возвращающая истину, если символ является мусорным, или ложь в противном случае. По умолчанию используется функция уровня модуля
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) -
Возвращает одну из двух последовательностей, из которых была создана дельта.
Для 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 (списки строк) и возвращает дельту (генератор, выдающий строки дельты) в унифицированном формате различий.
Унифицированные различия — это компактный способ показать только изменённые строки и несколько окружающих их контекстных строк. Изменения отображаются в строку (а не в виде отдельных блоков «до/после»). Количество контекстных строк задаётся параметром 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 считается игнорируемой, если она пуста или содержит один символ'#'; в противном случае она не считается игнорируемой. В старых версиях использовалась по умолчанию для параметра linejunk функцииndiff().
-
difflib.IS_CHARACTER_JUNK(ch) -
Возвращает
Trueдля игнорируемых символов. Символ ch считается игнорируемым, если это пробел или табуляция; в противном случае он не считается игнорируемым. Используется по умолчанию для параметра charjunk функцииndiff().
Объекты 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.
Объекты 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).Этот метод возвращает именованный кортеж
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) -
Вернуть генератор групп, содержащих до 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().
-
Три метода, возвращающие отношение числа совпадающих символов к их общему количеству, могут давать разные результаты из-за разной степени приближения, хотя значения 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может решать полезные задачи. -
Рецепт простой системы контроля версий для небольшого приложения, созданного с помощью
SequenceMatcher.
Пример 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.
""" 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
В этом примере показано, как использовать 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:])
© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/library/difflib.html