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)

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

Получив 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 (списки строк); возвращает различия (объект генератор, формирующий строки различий) в унифицированном формате.

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

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

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

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

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

Более подробный пример см. в разделе Интерфейс командной строки для difflib.

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

Сравнивает a и b (списки объектов 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 (по умолчанию) или быть функцией с одним аргументом, которая принимает элемент последовательности и возвращает 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'. Иными словами, из всех максимальных совпадающих блоков возвращается тот, который начинается раньше всех в 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).

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