Spec-Zone.ru › Python 3.7

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 — необязательные ключевые аргументы для указания строк заголовка столбцов файла «от»/«до» (оба по умолчанию пусты).

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

Изменено в версии 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)

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

Дана последовательность, сгенерированная функциями 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.

Объединённые diff — компактный способ показать только строки, которые были изменены, плюс несколько строк контекста. Изменения показаны встроеным стилем (вместо отдельных блоков до/после). Количество строк контекста задаётся параметром n, который по умолчанию равен трём.

По умолчанию строки управления diff (те, которые содержат ---, +++, или @@) создаются с заключительной новой строкой. Это полезно, чтобы входы, созданные с помощью io.IOBase.readlines(), привели к diff, пригодным для использования с io.IOBase.writelines(), так как и входы, и выходы содержат заключительные новые строки.

Для входов, которые не содержат заключительных новых строк, установите аргумент lineterm в значение "", чтобы выход был единообразно свободен от новых строк.

Формат объединённого diff обычно имеет заголовок с именами файлов и временами изменения. Любой или все из этих параметров могут быть заданы строками для 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().

См. также

Сопоставление шаблонов: гештальт-подход

Обсуждение подобного алгоритма Джона У. Рэтклиффа и Д. Э. Метценера. Это было опубликовано в журнале 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'. Другими словами, из всех максимальных совпадающих блоков верните тот, который начинается раньше всего в 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).

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() сначала, чтобы получить верхнюю границу.

Примечание

Внимание: результат вызова 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–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.7/library/difflib.html

Spec-Zone.ru

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