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