difflib — Справочные материалы для вычисления различий
Исходный код: Lib/difflib.py
Этот модуль предоставляет классы и функции для сравнения последовательностей. Он может использоваться, например, для сравнения файлов и может генерировать информацию о различиях в файлах в различных форматах, включая HTML, контекстные и унифицированные diff. Для сравнения каталогов и файлов также см. модуль filecmp.
-
class difflib.SequenceMatcher -
Это гибкий класс для сравнения пар последовательностей любого типа, при условии, что элементы последовательности являются хешируемыми. Основной алгоритм предшествует и немного сложнее, чем алгоритм, опубликованный в конце 1980-х годов Ратклиффом и Оберхельпом под гиперболическим названием «сопоставление гештальт-паттернов». Идея состоит в том, чтобы найти самую длинную непрерывную совпадающую подпоследовательность, которая не содержит «мусорных» элементов; эти «мусорные» элементы — это элементы, которые в некотором смысле неинтересны, такие как пустые строки или пробелы. (Обработка мусора — это расширение алгоритма Ратклиффа и Оберхельпа.) Затем та же идея применяется рекурсивно к частям последовательностей слева и справа от совпадающей подпоследовательности. Это не приводит к минимальным последовательностям редактирования, но обычно приводит к совпадениям, которые «выглядят правильно» для людей.
Время выполнения: Основной алгоритм Ратклиффа-Оберхельпа в худшем случае имеет кубическое время, а в ожидаемом случае — квадратичное время.
SequenceMatcherимеет квадратичное время в худшем случае и поведение в ожидаемом случае, зависящее сложным образом от того, сколько элементов у последовательностей общих; линейное время достигается в лучшем случае.Автоматическая эвристика мусора:
SequenceMatcherподдерживает эвристику, которая автоматически обрабатывает определенные элементы последовательности как мусор. Эвристика подсчитывает, сколько раз каждый отдельный элемент появляется в последовательности. Если дубликаты элемента (после первого) составляют более 1% от последовательности, и длина последовательности составляет не менее 200 элементов, этот элемент отмечается как «популярный» и обрабатывается как мусор в целях сопоставления последовательностей. Эту эвристику можно отключить, установив значение параметраautojunkв значениеFalseпри созданииSequenceMatcher.Введено в версии 3.2: Параметр autojunk.
-
class difflib.Differ -
Это класс для сравнения последовательностей строк текста и создания удобочитаемых различий или дельт. Differ использует
SequenceMatcherкак для сравнения последовательностей строк, так и для сравнения последовательностей символов в похожих (близких) строках.Каждая строка дельты
Differначинается с кода из двух букв:Код
Значение
'- 'строка, уникальная для последовательности 1
'+ 'строка, уникальная для последовательности 2
' 'строка, общая для обеих последовательностей
'? 'строка, отсутствующая в обеих входных последовательностях
Строки, начинающиеся с ‘
?’, пытаются направить взгляд на различия внутри строки и не присутствуют ни в одной из входных последовательностей. Эти строки могут быть запутанными, если последовательности содержат символы табуляции.
-
class difflib.HtmlDiff -
Этот класс можно использовать для создания HTML-таблицы (или полного HTML-файла, содержащего таблицу), отображающей попарное, построчное сравнение текста с выделением изменений между и внутри строк. Таблица может быть сгенерирована в режиме полного или контекстного сравнения различий.
Конструктор этого класса:
-
__init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK) -
Инициализирует экземпляр
HtmlDiff.tabsize — необязательный ключевой параметр для указания интервалов отступов табуляции, по умолчанию он равен
8.wrapcolumn — необязательный ключевой параметр для указания номера столбца, где строки прерываются и оборачиваются; по умолчанию строки не оборачиваются в
None.linejunk и charjunk — необязательные ключевые параметры, передаваемые в
ndiff()(используемыйHtmlDiffдля генерации попарного HTML-сравнения различий). См. документациюndiff()для значений по умолчанию и описаний параметров.
Следующие методы являются общедоступными:
-
make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5, *, charset='utf-8') -
Сравнивает fromlines и tolines (списки строк) и возвращает строку, которая является полным HTML-файлом, содержащим таблицу, отображающую построчные различия с выделением изменений между и внутри строк.
fromdesc и todesc — необязательные ключевые параметры для указания строк заголовков столбцов файлов «от»/«до» (оба по умолчанию пусты).
context и numlines — оба необязательные ключевые параметры. Установите context в
Trueдля отображения контекстных различий, в противном случае по умолчанию используетсяFalseдля отображения полных файлов. numlines по умолчанию равен5. Если context равенTrue, numlines управляет количеством контекстных строк, окружающих выделения различий. Если context равенFalse, numlines управляет количеством строк, отображаемых перед выделением различий при использовании гиперссылок «Далее» (установка значения нулю приведет к тому, что гиперссылки «Далее» будут помещать следующее выделение различий вверху браузера без какого-либо предваряющего контекста).Примечание
fromdesc и todesc интерпретируются как необработанный HTML и должны быть должным образом экранированы при получении входных данных из ненадежных источников.
Изменено в версии 3.5: Добавлен ключевой параметр charset. Значение кодировки символов HTML-документа по умолчанию изменилось с
'ISO-8859-1'на'utf-8'.
-
make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5) -
Сравнивает fromlines и tolines (списки строк) и возвращает строку, которая является полной HTML-таблицей, отображающей построчные различия с выделением изменений между и внутри строк.
Аргументы этого метода такие же, как и у метода
make_file().
Tools/scripts/diff.py— это командная оболочка для этого класса и содержит хороший пример его использования. -
-
difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n') -
Сравнивает a и b (списки строк); возвращает дельту (генератор, генерирующий строки дельты) в формате контекстного diff.
Контекстные diff — это компактный способ показать только строки, которые были изменены, плюс несколько строк контекста. Изменения отображаются в стиле «до/после». Количество строк контекста задается параметром n, который по умолчанию равен трём.
По умолчанию строки управления diff (те, что имеют
***или---) создаются с последующим символом новой строки. Это полезно, так как входы, созданные изio.IOBase.readlines(), приводят к diff, подходящим для использования сio.IOBase.writelines(), так как и входы, и выходы имеют символы новой строки в конце.Для входов, которые не имеют символов новой строки в конце, установите параметр lineterm в значение
"", чтобы вывод был однородно без символов новой строки.Формат контекстного diff обычно имеет заголовок для имён файлов и временных меток изменения. Любые или все из них могут быть заданы с помощью строк для fromfile, tofile, fromfiledate и tofiledate. Временные метки изменения обычно выражаются в формате ISO 8601. Если не указано, строки по умолчанию пустые.
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n'] >>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n'] >>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py', tofile='after.py')) *** before.py --- after.py *************** *** 1,4 **** ! bacon ! eggs ! ham guido --- 1,4 ---- ! python ! eggy ! hamster guido
См. Командная оболочка для difflib для более подробного примера.
-
difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6) -
Возвращает список лучших «достаточно хороших» совпадений. word — это последовательность, для которой требуются близкие совпадения (обычно строка), а possibilities — список последовательностей, с которыми нужно сопоставить word (обычно список строк).
Необязательный аргумент n (по умолчанию
3) — максимальное количество близких совпадений для возврата; n должно быть больше0.Необязательный аргумент cutoff (по умолчанию
0.6) — число с плавающей точкой в диапазоне [0, 1]. Возможные совпадения, которые не набирают по крайней мере такое же сходство с word, игнорируются.Лучшие (не более n) совпадения среди возможных возвращаются в списке, отсортированном по совпадению, наиболее похожее первым.
>>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy']) ['apple', 'ape'] >>> import keyword >>> get_close_matches('wheel', keyword.kwlist) ['while'] >>> get_close_matches('pineapple', keyword.kwlist) [] >>> get_close_matches('accept', keyword.kwlist) ['except']
-
difflib.ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK) -
Сравните a и b (списки строк); верните разность в стиле
Differ(генератор, генерирующий строки разницы).Необязательные ключевые параметры linejunk и charjunk — функции фильтрации (или
None):linejunk: Функция, принимающая одну строку в качестве аргумента и возвращающая True, если строка является мусором, или False, если нет. По умолчанию
None. Также есть функция модуляIS_LINE_JUNK(), которая фильтрует строки без видимых символов, кроме, возможно, одной символа решётки ('#') — однако базовый классSequenceMatcherвыполняет динамический анализ, какие строки настолько часты, что являются шумом, и это обычно работает лучше, чем использование этой функции.charjunk: Функция, принимающая символ (строку длиной 1) и возвращающая True, если символ является мусором, или False, если нет. По умолчанию функция модуля
IS_CHARACTER_JUNK(), которая фильтрует пробельные символы (пробел или табуляция; не стоит включать в это число переводы строки!).Tools/scripts/ndiff.pyявляется командной оболочкой для этой функции.>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True), ... 'ore\ntree\nemu\n'.splitlines(keepends=True)) >>> print(''.join(diff), end="") - one ? ^ + ore ? ^ - two - three ? - + tree + emu
-
difflib.restore(sequence, which) -
Возвращает одну из двух последовательностей, которые сгенерировали разность.
Принимая sequence, созданный с помощью
Differ.compare()илиndiff(), извлекает строки, исходящие из файла 1 или 2 (параметр which), удаляя префиксы строк.Пример:
>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True), ... 'ore\ntree\nemu\n'.splitlines(keepends=True)) >>> diff = list(diff) # materialize the generated delta into a list >>> print(''.join(restore(diff, 1)), end="") one two three >>> print(''.join(restore(diff, 2)), end="") ore tree emu
-
difflib.unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n') -
Сравните a и b (списки строк); верните разность (генератор, генерирующий строки разницы) в формате unified diff.
Объединённые разности — компактный способ показать только изменённые строки плюс несколько строк контекста. Изменения показаны встраиваемым стилем (вместо отдельных блоков «до» и «после»). Количество строк контекста задаётся параметром 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 (списки байтовых объектов) с помощью dfunc; выдайте последовательность строк разницы (также байтовые объекты) в формате, возвращаемом dfunc. dfunc должен быть вызываемым, обычно либо
unified_diff(), либоcontext_diff().Позволяет сравнивать данные с неизвестной или несовместимой кодировкой. Все входные данные, кроме n, должны быть байтовыми объектами, а не строками. Работает путём бескомпромиссного преобразования всех входных данных (кроме n) в строки и вызова
dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm). Вывод dfunc затем преобразуется обратно в байты, поэтому строки разницы, которые вы получаете, имеют такие же неизвестные/несовместимые кодировки, как a и b.Новое в версии 3.5.
-
difflib.IS_LINE_JUNK(line) -
Возвращает
Trueдля игнорируемых строк. Строка line игнорируема, если line пустая или содержит единственный'#', в противном случае она не игнорируема. Используется в качестве значения по умолчанию для параметра linejunk вndiff()в более старых версиях.
-
difflib.IS_CHARACTER_JUNK(ch) -
Возвращает
Trueдля игнорируемых символов. Символ ch игнорируем, если ch является пробелом или табуляцией, в противном случае он не игнорируем. Используется в качестве значения по умолчанию для параметра charjunk вndiff().
См. также
- Сопоставление шаблонов: подход гештальт
-
Обсуждение подобного алгоритма Джона У. Рэтклиффа и Д. Э. Метценера. Это было опубликовано в журнале Dr. Dobb's в июле 1988 года.
Объекты SequenceMatcher
Класс SequenceMatcher имеет такой конструктор:
-
class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True) -
Необязательный аргумент isjunk должен быть
None(по умолчанию) или функцией с одним аргументом, которая принимает элемент последовательности и возвращает true, если и только если элемент является «мусором» и должен быть проигнорирован. ПередачаNoneдля isjunk эквивалентна передачеlambda x: False; другими словами, элементы игнорируются. Например, передайте:lambda x: x in " \t"
если вы сравниваете строки как последовательности символов и не хотите синхронизироваться по пробелам или табуляциям.
Необязательные аргументы a и b — это сравниваемые последовательности; оба по умолчанию являются пустыми строками. Элементы обеих последовательностей должны быть хешируемыми.
Необязательный аргумент autojunk можно использовать для отключения автоматической эвристики мусора.
Введено в версии 3.2: Параметр autojunk.
Объекты SequenceMatcher получают три атрибута данных: bjunk — это множество элементов b, для которых isjunk является
True; bpopular — это множество элементов, не являющихся мусором, считаемых популярными эвристикой (если она не отключена); b2j — это словарь, сопоставляющий оставшиеся элементы b со списком позиций, где они встречаются. Все три значения сбрасываются всякий раз, когда b сбрасывается с помощьюset_seqs()илиset_seq2().Введено в версии 3.2: Атрибуты bjunk и bpopular.
SequenceMatcherобъекты имеют следующие методы:-
set_seqs(a, b) -
Установить две сравниваемые последовательности.
SequenceMatcherвычисляет и кеширует подробную информацию о второй последовательности, поэтому если вы хотите сравнить одну последовательность со многими последовательностями, используйтеset_seq2()для установки часто используемой последовательности один раз и вызывайтеset_seq1()многократно, один раз для каждой из других последовательностей.-
set_seq1(a) -
Установить первую последовательность для сравнения. Вторая последовательность для сравнения не изменяется.
-
set_seq2(b) -
Установить вторую последовательность для сравнения. Первая последовательность для сравнения не изменяется.
-
find_longest_match(alo=0, ahi=None, blo=0, bhi=None) -
Найти самый длинный совпадающий блок в
a[alo:ahi]иb[blo:bhi].Если isjunk был опущен или
None,find_longest_match()возвращает(i, j, k)такой, чтоa[i:i+k]равноb[j:j+k], гдеalo <= i <= i+k <= ahiиblo <= j <= j+k <= bhi. Для всех(i', j', k'), удовлетворяющих этим условиям, выполняются дополнительные условияk >= k',i <= i', и еслиi == i',j <= j'.>>> s = SequenceMatcher(None, " abcd", "abcd abcd") >>> s.find_longest_match(0, 5, 0, 9) Match(a=0, b=4, size=5)
Если isjunk был задан, сначала определяется самый длинный совпадающий блок, как описано выше, но с дополнительным ограничением, что ни один элемент мусора не появляется в блоке. Затем этот блок расширяется насколько возможно, сопоставляя (только) элементы мусора с обеих сторон. Таким образом, результирующий блок никогда не совпадает с мусором, кроме случаев, когда совпадающий мусор оказывается примыкающим к интересному совпадению.
Вот тот же пример, что и раньше, но рассматривая пробелы как мусор. Это предотвращает
' abcd'от совпадения с' abcd'в конце второй последовательности непосредственно. Вместо этого, только'abcd'может совпасть и совпадает с самым левым'abcd'во второй последовательности:>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd") >>> s.find_longest_match(0, 5, 0, 9) Match(a=1, b=0, size=4)
Если ни один блок не совпадает, возвращается
(alo, blo, 0).Этот метод возвращает кортеж с именами
Match(a, b, size).Изменено в версии 3.9: Добавлены значения по умолчанию.
-
get_matching_blocks() -
Возвращает список троек, описывающих неперекрывающиеся совпадающие подпоследовательности. Каждая тройка имеет вид
(i, j, n), и означает, чтоa[i:i+n] == b[j:j+n]. Тройки монотонно возрастают по i и j.Последняя тройка — фиктивная и имеет значение
(len(a), len(b), 0). Это единственная тройка сn == 0. Если(i, j, n)и(i', j', n')— смежные тройки в списке, и вторая не является последней тройкой в списке, тоi+n < i'илиj+n < j'; другими словами, смежные тройки всегда описывают несмежные равные блоки.>>> s = SequenceMatcher(None, "abxcd", "abcd") >>> s.get_matching_blocks() [Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]
-
get_opcodes() -
Возвращает список 5-кортежей, описывающих, как преобразовать a в b. Каждый кортеж имеет вид
(tag, i1, i2, j1, j2). Первый кортеж имеетi1 == j1 == 0, а последующие кортежи имеют i1 равное i2 предыдущего кортежа и, аналогично, j1 равное предыдущему j2.Значения tag — это строки со следующими значениями:
Значение
Значение
'replace'a[i1:i2]должно быть заменено наb[j1:j2].'delete'a[i1:i2]должно быть удалено. Обратите внимание, чтоj1 == j2в этом случае.'insert'b[j1:j2]должно быть вставлено вa[i1:i1]. Обратите внимание, чтоi1 == i2в этом случае.'equal'a[i1:i2] == b[j1:j2](подпоследовательности равны).Например:
>>> a = "qabxcd" >>> b = "abycdf" >>> s = SequenceMatcher(None, a, b) >>> for tag, i1, i2, j1, j2 in s.get_opcodes(): ... print('{:7} a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format( ... tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2])) delete a[0:1] --> b[0:0] 'q' --> '' equal a[1:3] --> b[0:2] 'ab' --> 'ab' replace a[3:4] --> b[2:3] 'x' --> 'y' equal a[4:6] --> b[3:5] 'cd' --> 'cd' insert a[6:6] --> b[5:6] '' --> 'f'
-
get_grouped_opcodes(n=3) -
Возвращает генератор групп с до n строк контекста.
Начиная с групп, возвращаемых
get_opcodes(), этот метод разбивает более мелкие кластеры изменений и устраняет промежуточные диапазоны, в которых нет изменений.Группы возвращаются в том же формате, что и
get_opcodes().
-
ratio() -
Возвращает меру сходства последовательностей как число с плавающей точкой в диапазоне [0, 1].
Где T — общее количество элементов в обеих последовательностях, а M — количество совпадений, это 2.0 * M / T. Обратите внимание, что это
1.0если последовательности идентичны и0.0если у них нет ничего общего.Это дорогостоящее вычисление, если
get_matching_blocks()илиget_opcodes()еще не вызывались, в этом случае вы можете попробоватьquick_ratio()илиreal_quick_ratio()сначала, чтобы получить верхнюю границу.Примечание
Внимание: результат вызова
ratio()может зависеть от порядка аргументов. Например:>>> SequenceMatcher(None, 'tide', 'diet').ratio() 0.25 >>> SequenceMatcher(None, 'diet', 'tide').ratio() 0.5
-
quick_ratio() -
Возвращает верхнюю границу
ratio()относительно быстро.
-
real_quick_ratio() -
Возвращает верхнюю границу
ratio()очень быстро.
-
Три метода, возвращающие коэффициент соответствия общему количеству символов, могут давать разные результаты из-за различных уровней приближения, хотя quick_ratio() и real_quick_ratio() всегда не меньше ratio():
>>> s = SequenceMatcher(None, "abcd", "bcde") >>> s.ratio() 0.75 >>> s.quick_ratio() 0.75 >>> s.real_quick_ratio() 1.0
Примеры SequenceMatcher
В этом примере сравниваются две строки, при этом пробелы считаются «мусором»:
>>> s = SequenceMatcher(lambda x: x == " ", ... "private Thread currentThread;", ... "private volatile Thread currentThread;")
ratio() возвращает число с плавающей точкой в диапазоне [0, 1], измеряя сходство последовательностей. Как правило, значение ratio() выше 0,6 означает, что последовательности являются близкими совпадениями:
>>> print(round(s.ratio(), 3)) 0.866
Если вас интересует только то, где последовательности совпадают, get_matching_blocks() очень удобен:
>>> for block in s.get_matching_blocks():
... print("a[%d] and b[%d] match for %d elements" % block)
a[0] and b[0] match for 8 elements
a[8] and b[17] match for 21 elements
a[29] and b[38] match for 0 elements
Обратите внимание, что последняя кортеж, возвращаемая get_matching_blocks() , всегда является «фиктивной» (len(a), len(b), 0), и это единственный случай, в котором последний элемент кортежа (количество совпавших элементов) 0.
Если вы хотите узнать, как изменить первую последовательность на вторую, используйте get_opcodes():
>>> for opcode in s.get_opcodes():
... print("%6s a[%d:%d] b[%d:%d]" % opcode)
equal a[0:8] b[0:8]
insert a[8:8] b[8:17]
equal a[8:29] b[17:38]
См. также
- Функция
get_close_matches()в этом модуле, которая демонстрирует, как простая программа, построенная на основеSequenceMatcher, может быть использована для выполнения полезной работы. -
Рецепт простой системы контроля версий для небольшого приложения, созданного с помощью
SequenceMatcher.
Объекты Differ
Обратите внимание, что сгенерированные Differ-дельты не претендуют на то, чтобы быть **минимальными** различиями. Наоборот, минимальные различия часто бывают неинтуитивными, поскольку они синхронизируются везде, где это возможно, иногда совпадают случайные совпадения, находящиеся в разных частях текста (например, 100 страницах). Ограничение точек синхронизации непрерывными совпадениями сохраняет некоторое понятие локальности, иногда с ценой увеличения длины различий.
Класс Differ имеет такой конструктор:
-
class difflib.Differ(linejunk=None, charjunk=None) -
Необязательные ключевые параметры linejunk и charjunk предназначены для функций фильтрации (или
None):linejunk: Функция, принимающая один строковый аргумент и возвращающая значение true, если строка является мусором. По умолчанию
None, что означает, что никакая строка не считается мусором.charjunk: Функция, принимающая один символьный аргумент (строка длиной 1) и возвращающая значение true, если символ является мусором. По умолчанию
None, что означает, что ни один символ не считается мусором.Эти функции фильтрации мусора ускоряют поиск различий и не приводят к игнорированию различающихся строк или символов. Прочитайте описание параметра isjunk метода
find_longest_match()для получения пояснения.Объекты
Differиспользуются (сгенерированы дельты) с помощью одного метода:-
compare(a, b) -
Сравнивает две последовательности строк и генерирует дельту (последовательность строк).
Каждая последовательность должна содержать отдельные строки с символом новой строки в конце. Такие последовательности можно получить из метода
readlines()объектах, похожих на файлы. Сгенерированная дельта также состоит из строк, завершенных символом новой строки, готовых к печати в том же виде с помощью методаwritelines()объекта, похожего на файл.
-
Пример Differ
В этом примере сравниваются два текста. Сначала мы задаем тексты, последовательности отдельных строк, заканчивающихся символом новой строки (такие последовательности также можно получить из метода readlines() объектов, похожих на файлы):
>>> text1 = ''' 1. Beautiful is better than ugly. ... 2. Explicit is better than implicit. ... 3. Simple is better than complex. ... 4. Complex is better than complicated. ... '''.splitlines(keepends=True) >>> len(text1) 4 >>> text1[0][-1] '\n' >>> text2 = ''' 1. Beautiful is better than ugly. ... 3. Simple is better than complex. ... 4. Complicated is better than complex. ... 5. Flat is better than nested. ... '''.splitlines(keepends=True)
Далее мы создаем объект Differ:
>>> d = Differ()
Обратите внимание, что при создании объекта Differ мы можем передать функции для фильтрации «мусора» строк и символов. Подробности см. в конструкторе Differ().
Наконец, мы сравниваем два текста:
>>> result = list(d.compare(text1, text2))
result — это список строк, поэтому давайте красиво его выведем:
>>> from pprint import pprint >>> pprint(result) [' 1. Beautiful is better than ugly.\n', '- 2. Explicit is better than implicit.\n', '- 3. Simple is better than complex.\n', '+ 3. Simple is better than complex.\n', '? ++\n', '- 4. Complex is better than complicated.\n', '? ^ ---- ^\n', '+ 4. Complicated is better than complex.\n', '? ++++ ^ ^\n', '+ 5. Flat is better than nested.\n']
Как одна многострочная строка, она выглядит так:
>>> import sys
>>> sys.stdout.writelines(result)
1. Beautiful is better than ugly.
- 2. Explicit is better than implicit.
- 3. Simple is better than complex.
+ 3. Simple is better than complex.
? ++
- 4. Complex is better than complicated.
? ^ ---- ^
+ 4. Complicated is better than complex.
? ++++ ^ ^
+ 5. Flat is better than nested.
Командная строка для difflib
Этот пример демонстрирует, как использовать difflib для создания утилиты, похожей на diff. Она также содержится в дистрибутиве исходного кода Python, как Tools/scripts/diff.py.
#!/usr/bin/env python3
""" Command line interface to difflib.py providing diffs in four formats:
* ndiff: lists every line and highlights interline changes.
* context: highlights clusters of changes in a before/after format.
* unified: highlights clusters of changes in an inline format.
* html: generates side by side comparison with change highlights.
"""
import sys, os, difflib, argparse
from datetime import datetime, timezone
def file_mtime(path):
t = datetime.fromtimestamp(os.stat(path).st_mtime,
timezone.utc)
return t.astimezone().isoformat()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-c', action='store_true', default=False,
help='Produce a context format diff (default)')
parser.add_argument('-u', action='store_true', default=False,
help='Produce a unified format diff')
parser.add_argument('-m', action='store_true', default=False,
help='Produce HTML side by side diff '
'(can use -c and -l in conjunction)')
parser.add_argument('-n', action='store_true', default=False,
help='Produce a ndiff format diff')
parser.add_argument('-l', '--lines', type=int, default=3,
help='Set number of context lines (default 3)')
parser.add_argument('fromfile')
parser.add_argument('tofile')
options = parser.parse_args()
n = options.lines
fromfile = options.fromfile
tofile = options.tofile
fromdate = file_mtime(fromfile)
todate = file_mtime(tofile)
with open(fromfile) as ff:
fromlines = ff.readlines()
with open(tofile) as tf:
tolines = tf.readlines()
if options.u:
diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
elif options.n:
diff = difflib.ndiff(fromlines, tolines)
elif options.m:
diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n)
else:
diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
sys.stdout.writelines(diff)
if __name__ == '__main__':
main()
© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/library/difflib.html