Spec-Zone.ru › Python 3.11

difflib — Помощники для вычисления различий

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

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

Примечание

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

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

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

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

Формат контекстного diff обычно имеет заголовок для имён файлов и времени модификации. Любые или все из них могут быть указаны с помощью строк для fromfile, tofile, fromfiledate и tofiledate. Время модификации обычно выражается в формате ISO 8601. Если не указаны, строки по умолчанию пустые.

>>> import sys
>>> from difflib import *
>>> 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). Возможности, которые не набирают не менее аналогичной похожести с 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 (те, у которых есть ---, +++, или @@ ) создаются с заключительным символом новой строки. Это полезно, чтобы вводимые данные, созданные из io.IOBase.readlines(), приводили к diff, пригодным для использования с io.IOBase.writelines(), так как и входные, и выходные данные имеют заключительные символы новой строки.

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

Формат unified 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.

New in version 3.5.

difflib.IS_LINE_JUNK(line)

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

difflib.IS_CHARACTER_JUNK(ch)

Возвращает True для игнорируемых символов. Символ ch игнорируется, если 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=0, ahi=None, blo=0, bhi=None)

Находит самый длинный совпадающий блок в 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).

Изменено в версии 3.9: Добавлены аргументы по умолчанию.

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–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/library/difflib.html

Spec-Zone.ru

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