Рубрики
Без рубрики

difflib – Сравнить последовательности

Автор оригинала: 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 работает с настраиваемыми классами, а также со встроенными типами, если они доступны для хеширования.

Смотрите также