Spec-Zone.ru › Python 3.12

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().

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. Если не указаны, строки по умолчанию пустые.

>>> 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 ) — число с плавающей точкой в диапазоне [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(), которая фильтрует пробельные символы (пробел или табуляция; не стоит включать в это список символы новой строки!).

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

По умолчанию, строки управления различиями (те, которые содержат ---, +++, или @@ ) создаются с заключительной новой строкой. Это полезно, чтобы вводимые данные, полученные с помощью io.IOBase.readlines(), приводили к различиям, подходящим для использования с 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 (списки объектов типа 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 игнорируется, если она пуста или содержит один '#', в противном случае она не игнорируется. Используется в качестве значения по умолчанию для параметра linejunk в ndiff() в более ранних версиях.

difflib.IS_CHARACTER_JUNK(ch)

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

См. также

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

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

Объекты SequenceMatcher

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

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

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

""" 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()

Пример ndiff

Этот пример показывает, как использовать difflib.ndiff().

"""ndiff [-q] file1 file2
    or
ndiff (-r1 | -r2) < ndiff_output > file1_or_file2

Print a human-friendly file difference report to stdout.  Both inter-
and intra-line differences are noted.  In the second form, recreate file1
(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.

In the first form, if -q ("quiet") is not specified, the first two lines
of output are

-: file1
+: file2

Each remaining line begins with a two-letter code:

    "- "    line unique to file1
    "+ "    line unique to file2
    "  "    line common to both files
    "? "    line not present in either input file

Lines beginning with "? " attempt to guide the eye to intraline
differences, and were not present in either input file.  These lines can be
confusing if the source files contain tab characters.

The first file can be recovered by retaining only lines that begin with
"  " or "- ", and deleting those 2-character prefixes; use ndiff with -r1.

The second file can be recovered similarly, but by retaining only "  " and
"+ " lines; use ndiff with -r2; or, on Unix, the second file can be
recovered by piping the output through

    sed -n '/^[+ ] /s/^..//p'
"""

__version__ = 1, 7, 0

import difflib, sys

def fail(msg):
    out = sys.stderr.write
    out(msg + "\n\n")
    out(__doc__)
    return 0

# open a file & return the file object; gripe and return 0 if it
# couldn't be opened
def fopen(fname):
    try:
        return open(fname)
    except IOError as detail:
        return fail("couldn't open " + fname + ": " + str(detail))

# open two files & spray the diff to stdout; return false iff a problem
def fcompare(f1name, f2name):
    f1 = fopen(f1name)
    f2 = fopen(f2name)
    if not f1 or not f2:
        return 0

    a = f1.readlines(); f1.close()
    b = f2.readlines(); f2.close()
    for line in difflib.ndiff(a, b):
        print(line, end=' ')

    return 1

# crack args (sys.argv[1:] is normal) & compare;
# return false iff a problem

def main(args):
    import getopt
    try:
        opts, args = getopt.getopt(args, "qr:")
    except getopt.error as detail:
        return fail(str(detail))
    noisy = 1
    qseen = rseen = 0
    for opt, val in opts:
        if opt == "-q":
            qseen = 1
            noisy = 0
        elif opt == "-r":
            rseen = 1
            whichfile = val
    if qseen and rseen:
        return fail("can't specify both -q and -r")
    if rseen:
        if args:
            return fail("no args allowed with -r option")
        if whichfile in ("1", "2"):
            restore(whichfile)
            return 1
        return fail("-r value must be 1 or 2")
    if len(args) != 2:
        return fail("need 2 filename args")
    f1name, f2name = args
    if noisy:
        print('-:', f1name)
        print('+:', f2name)
    return fcompare(f1name, f2name)

# read ndiff output from stdin, and print file1 (which=='1') or
# file2 (which=='2') to stdout

def restore(which):
    restored = difflib.restore(sys.stdin.readlines(), which)
    sys.stdout.writelines(restored)

if __name__ == '__main__':
    main(sys.argv[1:])

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/library/difflib.html

Spec-Zone.ru

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