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