Spec-Zone.ru › Python 3.5

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.

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

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

Для входных данных, не имеющих trailing newlines, установите аргумент 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 (списки объектов типа bytes) с помощью dfunc; сгенерируйте последовательность строк дельты (также типа bytes) в формате, возвращаемом dfunc. dfunc должен быть вызываемым объектом, обычно либо unified_diff(), либо context_diff().

Позволяет сравнивать данные с неизвестной или несовместимой кодировкой. Все входные данные, кроме n, должны быть объектами типа bytes, а не str. Работает путём бессознательной конвертации всех входных данных (кроме n) в str и вызовом dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm). Выходные данные dfunc затем преобразуются обратно в bytes, поэтому строки дельты, которые вы получаете, имеют те же неизвестные/несовместимые кодировки, что и a и b.

Введено в версии 3.5.

difflib.IS_LINE_JUNK(line)

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

difflib.IS_CHARACTER_JUNK(ch)

Возвращает True для игнорируемых символов. Символ ch игнорируется, если ch является пробелом или табуляцией, иначе он не игнорируется. Используется по умолчанию для параметра charjunk в ndiff().

См. также

Сопоставление шаблонов: подход гештальт
Обсуждение аналогичного алгоритма Джона В. Рэтклиффа и Д. Э. Метценера. Он был опубликован в Dr. Dobb’s Journal в июле 1988 года.

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

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.

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() объекта, похожего на файл.

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.

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, time, 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.5/library/difflib.html

Spec-Zone.ru

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