Spec-Zone.ru › Python 3.14

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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API