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: Функция, принимающая одну строку и возвращающая истину, если строка является мусором, или ложь в противном случае. Значение по умолчанию —
None. Также существует функция уровня модуляIS_LINE_JUNK(), которая отфильтровывает строки без видимых символов, за исключением не более чем одного символа решетки ('#'). Однако базовый классSequenceMatcherдинамически анализирует, какие строки встречаются настолько часто, что становятся шумом; обычно этот способ работает лучше, чем использование данной функции.charjunk: Функция, принимающая символ (строку длиной 1) и возвращающая истину, если символ является мусором, или ложь в противном случае. Значение по умолчанию — функция уровня модуля
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(значение по умолчанию) или быть функцией с одним аргументом, которая принимает элемент последовательности и возвращает истину тогда и только тогда, когда элемент является «мусором» и его следует игнорировать. Передача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() -
Возвращает список пятерок, описывающих, как преобразовать 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–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/library/difflib.html