Spec-Zone.ru › Python 3.8

difflib — Справочные материалы для вычисления различий

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

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

class difflib.SequenceMatcher

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

Время выполнения: Основной алгоритм Рэтклиффа-Обелсхёльпа в худшем случае имеет кубическую сложность, а в ожидаемом случае — квадратическую. 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 — это необязательные ключевые аргументы для указания строк заголовков столбцов файла «from»/«to» (оба по умолчанию равны пустой строке).

context и numlines — оба необязательные ключевых аргумента. Установите context в значение True при отображении контекстных различий, в противном случае используется значение по умолчанию False для отображения полных файлов. numlines по умолчанию равно 5. При значении context True значение numlines контролирует количество контекстных строк, окружающих выделения различий. При значении context False значение numlines контролирует количество строк, отображаемых перед выделением различий при использовании гиперссылок «следующая» (значение, равное нулю, приведет к тому, что гиперссылки «следующая» разместят следующее выделение различий в верхней части браузера без какого-либо предшествующего контекста).

Примечание

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 (списки строк); возвращает дельту (генератор, генерирующий строки дельты) в формате контекстного диффа.

Контекстные диффы — это компактный способ отображения только строк, которые изменились, плюс несколько строк контекста. Изменения показаны в стиле «до/после». Количество строк контекста задается параметром 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(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: Функция, принимающая один строковый аргумент и возвращающая значение true, если строка является мусором, или false, если нет. По умолчанию — None. Также есть функция модуля IS_LINE_JUNK(), которая отфильтровывает строки без видимых символов, кроме, возможно, одной строки с символом фунта ('#'). Впрочем, внутренний класс SequenceMatcher динамически анализирует строки, которые настолько часто встречаются, что являются шумом, и это обычно работает лучше, чем использование этой функции.

charjunk: Функция, принимающая символ (строка длиной 1) и возвращающая значение true, если символ является мусором, или 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)

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

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

Unified diff — это компактный способ показать только изменённые строки плюс несколько строк контекста. Изменения показаны в строчном стиле (вместо отдельных блоков до/после). Количество строк контекста задаётся параметром 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.

New in version 3.5.

difflib.IS_LINE_JUNK(line)

Возвращает True для игнорируемых строк. Строка line игнорируется, если она пуста или содержит один '#', в противном случае она не игнорируется. Используется по умолчанию для параметра linejunk в ndiff() в более старых версиях.

difflib.IS_CHARACTER_JUNK(ch)

Возвращает True для игнорируемых символов. Символ 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 — это сравниваемые последовательности; по умолчанию обе являются пустыми строками. Элементы обеих последовательностей должны быть хешируемыми.

Необязательный аргумент 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, ahi, blo, bhi)

Найти самую длинную совпадающую область в 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'.

>>> 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).

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)

Возвращает генератор групп с максимальным количеством строк контекста.

Начиная с групп, возвращаемых 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, может использоваться для выполнения полезной работы.
  • Рецепт простой системы управления версиями для небольшого приложения, построенного с помощью SequenceMatcher.

Объекты Differ

Обратите внимание, что сгенерированные 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() объекта типа «поток данных».

Пример 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. Она также входит в дистрибутив 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()

© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/library/difflib.html

Spec-Zone.ru

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