Spec-Zone.ru › Python 3.6

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

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

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, должны быть объектами байтов, а не строками. Работает, бессозначно преобразуя все входные данные (кроме n) в строки и вызывая 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().

См. также

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

Обсуждение аналогичного алгоритма Джона У. Рэтклиффа и Д. Э. Метценера. Это было опубликовано в Журнале доктора Доббса в июле 1988 года.

6.3.1. Объекты SequenceMatcher

Класс SequenceMatcher имеет такой конструктор:

class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)

Дополнительный аргумент isjunk должен быть None (по умолчанию) или функцией с одним аргументом, которая принимает элемент последовательности и возвращает true, если и только если элемент является «мусором» и должен быть пропущен. Передача None для isjunk эквивалентна передаче lambda x: 0; другими словами, ни один элемент не игнорируется. Например, передайте:

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

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

6.3.2. Примеры 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.

6.3.3. Объекты 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() объекта, подобного файлу.

6.3.4. Пример использования 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.

6.3.5. Интерфейс командной строки для 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.6/library/difflib.html

Spec-Zone.ru

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