Автор оригинала: Doug Hellmann.
Цель:
Сравните последовательности, особенно строки текста.
Модуль difflib
содержит инструменты для вычисления и работы с различиями между последовательностями. Он особенно полезен для сравнения текста и включает функции, которые создают отчеты с использованием нескольких общих форматов различий.
Все примеры в этом разделе будут использовать эти общие тестовые данные в модуле difflib_data.py
:
difflib_data.py
text1 """Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convalis. Nam sed sem vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus.""" text1_lines text1.splitlines() text2 """Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convalis. Nam cras vitae mi vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus. Suspendisse eu lectus. In nunc.""" text2_lines text2.splitlines()
Сравнение фрагментов текста
Класс Differ
работает с последовательностями текстовых строк и создает удобочитаемые дельты или инструкции по изменению, включая различия в отдельных строках. Вывод по умолчанию, производимый Differ
, аналогичен инструменту командной строки diff
в Unix. Он включает исходные входные значения из обоих списков, включая общие значения и данные разметки, чтобы указать, какие изменения были внесены.
- Строки с префиксом
-
были в первой последовательности, но не во второй. - Строки с префиксом
+
были во второй последовательности, но не в первой. - Если в строке есть возрастающая разница между версиями, дополнительная строка с префиксом
?
используется для выделения изменения в новой версии. - Если строка не изменилась, она печатается с дополнительным пустым пространством в левом столбце, чтобы выровнять ее с другим выводом, который может отличаться.
Разбиение текста на последовательность отдельных строк перед его передачей в compare ()
дает более читаемый вывод, чем передача больших строк.
difflib_differ.py
import difflib from difflib_data import * d difflib.Differ() diff d.compare(text1_lines, text2_lines) print('\n'.join(diff))
Начало обоих текстовых сегментов в данных примера одинаково, поэтому первая строка печатается без каких-либо дополнительных аннотаций.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec
Третья строка данных была изменена, чтобы включить в измененный текст запятую. Обе версии строки печатаются с дополнительной информацией в строке 5, показывающей столбец, в котором текст был изменен, включая тот факт, что был добавлен символ ,
.
- pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis + pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis ? +
Следующие несколько строк вывода показывают, что лишнее пространство было удалено.
- pharetra tortor. In nec mauris eget magna consequat ? - + pharetra tortor. In nec mauris eget magna consequat
Затем было внесено более сложное изменение, заменив несколько слов во фразе.
- convalis. Nam sed sem vitae odio pellentesque interdum. Sed ? - -- + convalis. Nam cras vitae mi vitae odio pellentesque interdum. Sed ? +++ +++++ +
Последнее предложение в абзаце было значительно изменено, поэтому разница заключается в удалении старой версии и добавлении новой.
consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta - adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate - tristique enim. Donec quis lectus a justo imperdiet tempus. + adipiscing. Duis vulputate tristique enim. Donec quis lectus a + justo imperdiet tempus. Suspendisse eu lectus. In nunc.
Функция ndiff ()
выдает практически такой же результат. Обработка специально предназначена для работы с текстовыми данными и устранения «шума» на входе.
Другие форматы вывода
В то время как класс Differ
показывает все входные строки, унифицированное различие включает только измененные строки и немного контекста. Функция unified_diff ()
производит такой вывод.
difflib_unified.py
import difflib from difflib_data import * diff difflib.unified_diff( text1_lines, text2_lines, lineterm'', ) print('\n'.join(diff))
Аргумент lineterm
используется для указания unified_diff ()
пропустить добавление новой строки к возвращаемым им строкам управления, поскольку строки ввода не включают их. Новые строки добавляются ко всем строкам при печати. Результат должен быть знаком пользователям многих популярных инструментов управления версиями.
$ python3 difflib_unified.py --- +++ @@ -1,11 +1,11 @@ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Integer eu lacus accumsan arcu fermentum euismod. Donec -pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis -pharetra tortor. In nec mauris eget magna consequat -convalis. Nam sed sem vitae odio pellentesque interdum. Sed +pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis +pharetra tortor. In nec mauris eget magna consequat +convalis. Nam cras vitae mi vitae odio pellentesque interdum. S ed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta -adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate -tristique enim. Donec quis lectus a justo imperdiet tempus. +adipiscing. Duis vulputate tristique enim. Donec quis lectus a +justo imperdiet tempus. Suspendisse eu lectus. In nunc.
Использование context_diff ()
дает аналогичный читаемый вывод.
Нежелательные данные
Все функции, производящие разностные последовательности, принимают аргументы, указывающие, какие строки следует игнорировать, а какие символы в строке следует игнорировать. Эти параметры можно использовать, например, для пропуска изменений разметки или пробелов в двух версиях файла.
difflib_junk.py
# This example is adapted from the source for difflib.py. from difflib import SequenceMatcher def show_results(match): print(' a = {}'.format(match.a)) print(' b = {}'.format(match.b)) print(' size = {}'.format(match.size)) i, j, k match print(' A[a:a+size] = {!r}'.format(A[i:i + k])) print(' B[b:b+size] = {!r}'.format(B[j:j + k])) A " abcd" B "abcd abcd" print('A = {!r}'.format(A)) print('B = {!r}'.format(B)) print('\nWithout junk detection:') s1 SequenceMatcher(None, A, B) match1 s1.find_longest_match(0, len(A), 0, len(B)) show_results(match1) print('\nTreat spaces as junk:') s2 SequenceMatcher(lambda x: x " ", A, B) match2 s2.find_longest_match(0, len(A), 0, len(B)) show_results(match2)
По умолчанию Differ
не игнорирует какие-либо строки или символы явно, а полагается на способность SequenceMatcher
обнаруживать шум. По умолчанию для ndiff ()
игнорируются пробелы и символы табуляции.
$ python3 difflib_junk.py A = ' abcd' B = 'abcd abcd' Without junk detection: a = 0 b = 4 size = 5 A[a:a+size] = ' abcd' B[b:b+size] = ' abcd' Treat spaces as junk: a = 1 b = 0 size = 4 A[a:a+size] = 'abcd' B[b:b+size] = 'abcd'
Сравнение произвольных типов
Класс SequenceMatcher
сравнивает две последовательности любых типов, если значения являются хешируемыми. Он использует алгоритм для определения самых длинных смежных совпадающих блоков из последовательностей, устраняя «ненужные» значения, которые не влияют на реальные данные.
Функция get_opcodes ()
возвращает список инструкций для изменения первой последовательности, чтобы она соответствовала второй. Инструкции кодируются как пятиэлементные кортежи, включая строковую инструкцию («код операции», см. Таблицу ниже) и две пары начальных и конечных индексов в последовательности (обозначаемые как i1
, i2
, j1
и j2
).
diffflib.get_opcodes () Инструкции
Код операции
Определение
‘заменять’
Заменять
a [i1: i2]
с
б [j1: j2]
‘Удалить’
Удалять
a [i1: i2]
полностью
‘вставлять’
Вставлять
б [j1: j2]
в
a [i1: i1]
‘равный’
Подпоследовательности уже равны
difflib_seq.py
import difflib s1 [1, 2, 3, 5, 6, 4] s2 [2, 3, 5, 4, 6, 1] print('Initial data:') print('s1 =', s1) print('s2 =', s2) print('s1, s1 s2) print() matcher difflib.SequenceMatcher(None, s1, s2) for tag, i1, i2, j1, j2 in reversed(matcher.get_opcodes()): if tag 'delete': print('Remove {} from positions [{}:{}]'.format( s1[i1:i2], i1, i2)) print(' before =', s1) del s1[i1:i2] elif tag 'equal': print('s1[{}:{}] and s2[{}:{}] are the same'.format( i1, i2, j1, j2)) elif tag 'insert': print('Insert {} from s2[{}:{}] into s1 at {}'.format( s2[j1:j2], j1, j2, i1)) print(' before =', s1) s1[i1:i2] s2[j1:j2] elif tag 'replace': print(('Replace {} from s1[{}:{}] ' 'with {} from s2[{}:{}]').format( s1[i1:i2], i1, i2, s2[j1:j2], j1, j2)) print(' before =', s1) s1[i1:i2] s2[j1:j2] print(' after =', s1, '\n') print('s1, s1 s2)
В этом примере сравниваются два списка целых чисел и используются get_opcodes ()
для получения инструкций по преобразованию исходного списка в более новую версию. Модификации применяются в обратном порядке, чтобы индексы списка оставались точными после добавления и удаления элементов.
$ python3 difflib_seq.py Initial data: s1 = [1, 2, 3, 5, 6, 4] s2 = [2, 3, 5, 4, 6, 1] s1 Replace [4] from s1[5:6] with [1] from s2[5:6] before = [1, 2, 3, 5, 6, 4] after = [1, 2, 3, 5, 6, 1] s1[4:5] and s2[4:5] are the same after = [1, 2, 3, 5, 6, 1] Insert [4] from s2[3:4] into s1 at 4 before = [1, 2, 3, 5, 6, 1] after = [1, 2, 3, 5, 4, 6, 1] s1[1:4] and s2[0:3] are the same after = [1, 2, 3, 5, 4, 6, 1] Remove [1] from positions [0:1] before = [1, 2, 3, 5, 4, 6, 1] after = [2, 3, 5, 4, 6, 1] s1
SequenceMatcher
работает с настраиваемыми классами, а также со встроенными типами, если они доступны для хеширования.
Смотрите также
- стандартная библиотечная документация для difflib
- «Сопоставление с образцом: гештальт-подход» – обсуждение аналогичного алгоритма Джоном W. Ratcliff и DE Metzener опубликовали в Dr. Dobb’s Journal в июле 1988 г.