Содержание страницы
difflib – Средства для вычисления различий¶difflib – Helpers for computing deltas
Исходный код: Lib/difflib.py
Этот модуль предоставляет классы и функции для сравнения последовательностей. Он может использоваться, например, для сравнения файлов и может выдавать информацию о различиях файлов в различных форматах, включая HTML, контекстные и унифицированные различия. Для сравнения каталогов и файлов см. также модуль filecmp.
- class difflib.SequenceMatcher
Это гибкий класс для сравнения пар последовательностей любого типа, при условии, что элементы последовательностей хешируемы. Базовый алгоритм предшествует и немного сложнее, чем алгоритм, опубликованный в конце 1980-х годов Рэтклиффом и Обершелпом под гиперболическим названием «gestalt pattern matching». Идея заключается в нахождении самой длинной непрерывной совпадающей подпоследовательности, не содержащей «мусорных» элементов; эти «мусорные» элементы – те, которые в некотором смысле неинтересны, например, пустые строки или пробелы. (Обработка мусора является расширением алгоритма Рэтклиффа и Обершелпа.) Та же идея затем рекурсивно применяется к частям последовательностей слева и справа от совпадающей подпоследовательности. Это не даёт минимальных последовательностей редактирования, но, как правило, даёт совпадения, которые «выглядят правильно» для человека.
Время выполнения: Базовый алгоритм Рэтклиффа-Обершелпа имеет кубическое время в худшем случае и квадратичное время в ожидаемом.
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 (списки строк); возвращает дельту (генератор, порождающий строки дельты) в формате контекстного diff.
Контекстные диффы – это компактный способ показать только изменённые строки плюс несколько строк контекста. Изменения отображаются в стиле «до/после». Количество строк контекста задаётся параметром n, по умолчанию – три.
По умолчанию управляющие строки diff (те, что содержат
***или---) создаются с завершающим переводом строки. Это удобно, так как входные данные, созданные с помощьюio.IOBase.readlines(), дают diff, подходящие для использования сio.IOBase.writelines(), поскольку и входные, и выходные данные имеют завершающие переводы строк.Для входных данных, не имеющих завершающего перевода строки, установите аргумент lineterm равным
"", чтобы выходные данные были полностью без символов новой строки.Формат контекстного diff обычно содержит заголовок с именами файлов и временем изменения. Любые или все из них могут быть заданы строками для 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) # материализовать сгенерированную дельту в список >>> 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, который по умолчанию равен трём.
По умолчанию управляющие строки diff (те, что содержат
---,+++или@@) создаются с завершающим переводом строки. Это удобно, так как входные данные, созданные с помощьюio.IOBase.readlines(), дают diff, подходящие для использования сio.IOBase.writelines(), поскольку и входные, и выходные данные имеют завершающие переводы строк.Для входных данных, не имеющих завершающего перевода строки, установите аргумент lineterm равным
"", чтобы выходные данные были полностью без символов новой строки.Формат unified 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 (списки объектов 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 считается игнорируемой, если line пуста или содержит только'#', в противном случае она не игнорируется. Используется как значение по умолчанию для параметра linejunk вndiff()в старых версиях.
- difflib.IS_CHARACTER_JUNK(ch)¶
Возвращает
Trueдля символов, которые можно игнорировать. Символ ch считается игнорируемым, если ch является пробелом или табуляцией, в противном случае он не игнорируется. Используется как значение по умолчанию для параметра charjunk вndiff().
См. также
- Сопоставление с образцом: гештальт-подход
Обсуждение аналогичного алгоритма Джона У. Рэтклиффа и Д. Э. Мецнера. Опубликовано в журнале Dr. Dobb’s Journal в июле 1988 года.
Объекты SequenceMatcher¶SequenceMatcher objects
Класс SequenceMatcher имеет следующий конструктор:
- class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)¶
Необязательный аргумент isjunk должен быть
None(значение по умолчанию) или функцией с одним аргументом, которая принимает элемент последовательности и возвращает true, если и только если элемент является «мусором» (junk) и его следует игнорировать. ПередачаNoneдля isjunk эквивалентна передачеlambda x: False; иными словами, никакие элементы не игнорируются. Например, передайте:lambda x: x in " \t"
если строки сравниваются как последовательности символов и не требуется выравнивание по пробелам или табуляциям.
Необязательные аргументы a и b – это последовательности для сравнения; обе по умолчанию равны пустым строкам. Элементы обеих последовательностей должны быть хэшируемыми (hashable).
Необязательный аргумент 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() и 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¶SequenceMatcher examples
В этом примере сравниваются две строки, при этом пробелы считаются «мусором»:
>>> 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 objects
Обратите внимание, что дельты, сгенерированные Differ, не претендуют на то, чтобы быть минимальными различиями. Напротив, минимальные различия часто противоречат интуиции, поскольку они синхронизируются везде, где возможно, иногда случайно сопоставляя фрагменты, отстоящие на 100 страниц. Ограничение точек синхронизации смежными совпадениями сохраняет некоторое понятие локальности, ценой иногда более длинного diff.
Класс Differ имеет следующий конструктор:
- class difflib.Differ(linejunk=None, charjunk=None)
Необязательные именованные параметры linejunk и charjunk предназначены для функций фильтрации (или
None):linejunk: функция, которая принимает один строковый аргумент и возвращает истину, если строка является мусорной. По умолчанию –
None, то есть ни одна строка не считается мусорной.charjunk: функция, которая принимает один символьный аргумент (строку длиной 1) и возвращает истину, если символ является мусорным. По умолчанию –
None, то есть ни один символ не считается мусорным.Эти функции фильтрации мусора ускоряют сопоставление для поиска различий и не приводят к игнорированию каких-либо различающихся строк или символов. Прочтите описание параметра isjunk метода
find_longest_match()для объяснения.Объекты
Differиспользуются (генерируют дельты) через единственный метод:- compare(a, b)¶
Сравнивает две последовательности строк и генерирует дельту (последовательность строк).
Каждая последовательность должна содержать отдельные однострочные строки, заканчивающиеся символами новой строки. Такие последовательности можно получить из метода
readlines()файлоподобных объектов. Сгенерированная дельта также состоит из строк, завершающихся новой строкой, готовых к печати как есть с помощью методаwritelines()файлоподобного объекта.
Пример Differ¶Differ example
Этот пример сравнивает два текста. Сначала мы задаем тексты – последовательности отдельных однострочных строк, заканчивающихся символами новой строки (такие последовательности также можно получить из метода 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¶A command-line interface to difflib
Этот пример показывает, как использовать difflib для создания утилиты, подобной diff.
""" Интерфейс командной строки для difflib.py, предоставляющий различия в четырёх форматах:
* ndiff: выводит каждую строку и подсвечивает изменения между строками.
* context: подсвечивает кластеры изменений в формате «до/после».
* unified: подсвечивает кластеры изменений во встроенном формате.
* html: генерирует построчное сравнение с подсветкой изменений.
"""
import sys, os, difflib, argparse
import datetime as dt
def file_mtime(path):
t = dt.datetime.fromtimestamp(os.stat(path).st_mtime,
dt.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¶ndiff example
Этот пример показывает, как использовать difflib.ndiff().
"""ndiff [-q] file1 file2
или
ndiff (-r1 | -r2) < ndiff_output > file1_or_file2
Выводит в stdout понятный человеку отчёт о различиях файлов. Отмечаются как меж-
строчные, так и внутристрочные различия. Во второй форме воссоздаёт file1
(-r1) или file2 (-r2) в stdout на основе отчёта ndiff со stdin.
В первой форме, если не указан -q ("quiet"), первые две строки
вывода – это
-: file1
+: file2
Каждая следующая строка начинается с двухбуквенного кода:
"- " строка, уникальная для file1
"+ " строка, уникальная для file2
" " строка, общая для обоих файлов
"? " строка, отсутствующая в обоих входных файлах
Строки, начинающиеся с "? ", помогают глазу заметить внутристрочные
различия; в исходных файлах таких строк нет. Эти строки могут
сбивать с толку, если исходные файлы содержат символы табуляции.
Первый файл можно восстановить, оставив только строки, начинающиеся с
" " " или "- ", и удалив эти двухсимвольные префиксы; используйте ndiff с -r1.
Второй файл восстанавливается аналогично, но следует оставить только " " и
""+ "; используйте ndiff с -r2; или, в Unix, второй файл можно
восстановить, пропустив вывод через
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
# открыть файл и вернуть файловый объект; ругать и вернуть 0, если не
# удалось открыть
def fopen(fname):
try:
return open(fname)
except IOError as detail:
return fail("couldn't open " + fname + ": " + str(detail))
# открыть два файла и выдать diff в stdout; вернуть false при проблеме
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
# разобрать args (обычно sys.argv[1:]) и сравнить;
# вернуть false при проблеме
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)
# прочитать вывод ndiff со stdin и вывести file1 (which=='1') или
# file2 (which=='2') в stdout
def restore(which):
restored = difflib.restore(sys.stdin.readlines(), which)
sys.stdout.writelines(restored)
if __name__ == '__main__':
main(sys.argv[1:])