Spec-Zone.ru › Python 3.9

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

Примечание

fromdesc и todesc интерпретируются как неэкранированный HTML и должны быть надлежащим образом экранированы при получении входных данных из ненадежных источников.

Изменено в версии 3.5: Добавлен ключевой аргумент charset. По умолчанию кодировка HTML-документа изменилась с 'ISO-8859-1' на 'utf-8'.

make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)

Сравнивает fromlines и tolines (списки строк) и возвращает строку, которая представляет собой HTML-таблицу, отображающую построчные различия с выделением изменений между строками и внутри строк.

Аргументы для этого метода такие же, как для метода make_file().

Tools/scripts/diff.py — это командная оболочка для этого класса и содержит хороший пример его использования.

difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')

Сравните a и b (списки строк); верните дельту (генератор, генерирующий строки дельты) в формате контекстного диффа.

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

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

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

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

>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py', tofile='after.py'))
*** before.py
--- after.py
***************
*** 1,4 ****
! bacon
! eggs
! ham
  guido
--- 1,4 ----
! python
! eggy
! hamster
  guido

См. Командный интерфейс difflib для более подробного примера.

difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)

Возвращает список наилучших «достаточно хороших» совпадений. word — это последовательность, для которой требуются близкие совпадения (обычно строка), а possibilities — список последовательностей, с которыми необходимо сопоставить word (обычно список строк).

Необязательный аргумент n (по умолчанию 3) — максимальное количество близких совпадений для возврата; n должно быть больше 0.

Необязательный аргумент cutoff (по умолчанию 0.6 — это число с плавающей точкой в диапазоне [0, 1]. Возможности, которые не набирают по крайней мере такое же сходство с word, игнорируются.

Наилучшие (не более n) совпадения среди возможных возвращаются в списке, отсортированном по совпадению, наиболее похожее первым.

>>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'])
['apple', 'ape']
>>> import keyword
>>> get_close_matches('wheel', keyword.kwlist)
['while']
>>> get_close_matches('pineapple', keyword.kwlist)
[]
>>> get_close_matches('accept', keyword.kwlist)
['except']
difflib.ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK)

Сравните a и b (списки строк); верните дельту в стиле Differ (генератор, генерирующий строки дельты).

Необязательные ключевые параметры linejunk и charjunk являются функциями фильтрации (или None) :

linejunk: Функция, принимающая строку в качестве аргумента и возвращающая true, если строка является мусором, или false, если нет. По умолчанию это None. Также есть функция на уровне модуля IS_LINE_JUNK(), которая отфильтровывает строки без видимых символов, кроме, возможно, одной строки с символом # ('#') – однако, базовый класс SequenceMatcher выполняет динамический анализ, определяя, какие строки настолько часто встречаются, что представляют собой шум, и это обычно работает лучше, чем использование этой функции.

charjunk: Функция, принимающая символ (строку длиной 1) и возвращающая true, если символ является мусором, или false, если нет. По умолчанию используется функция на уровне модуля IS_CHARACTER_JUNK(), которая отфильтровывает пробельные символы (пробел или табуляция; не рекомендуется включать в список перевод строки!).

Tools/scripts/ndiff.py — это командная оболочка для этой функции.

>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
...              'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> print(''.join(diff), end="")
- one
?  ^
+ ore
?  ^
- two
- three
?  -
+ tree
+ emu
difflib.restore(sequence, which)

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

Используя sequence, полученный из Differ.compare() или ndiff(), извлеките строки, происходящие из файла 1 или 2 (параметр which), удаляя префиксы строк.

Пример:

>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
...              'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> diff = list(diff) # materialize the generated delta into a list
>>> print(''.join(restore(diff, 1)), end="")
one
two
three
>>> print(''.join(restore(diff, 2)), end="")
ore
tree
emu
difflib.unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')

Сравните a и b (списки строк); верните дельту (генератор, генерирующий строки дельты) в формате unified diff.

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

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

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

Формат context 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 игнорируется, если 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–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/library/difflib.html

Spec-Zone.ru

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