Содержание страницы
6.3. difflib – Вспомогательные средства для вычисления дельт¶difflib – Helpers for computing deltas
Этот модуль предоставляет классы и функции для сравнения последовательностей. Его можно использовать, например, для сравнения файлов, и он может выводить информацию о различиях в различных форматах, включая 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)¶
Сравнивает fromlines и tolines (списки строк) и возвращает строку, содержащую полный HTML-файл с таблицей, отображающей построчные различия с подсветкой изменений между строками и внутри строк.
fromdesc и todesc – необязательные именованные аргументы для задания строк заголовков столбцов исходного и целевого файлов (оба по умолчанию равны пустой строке).
context и numlines – необязательные именованные аргументы. Установите context в значение True, чтобы показывать контекстные различия, иначе по умолчанию False, показывающее полные файлы. numlines по умолчанию равен 5. Когда context равно True, numlines управляет количеством строк контекста, окружающих подсветку различий. Когда context равно False, numlines управляет количеством строк, показываемых перед подсветкой различий при использовании гиперссылок «далее» (установка нуля приведёт к тому, что гиперссылка «далее» будет помещать следующую подсветку различий в верхнюю часть браузера без предшествующего контекста).
- 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.
Контекстные диффы – это компактный способ показать только изменённые строки плюс несколько строк контекста. Изменения отображаются в стиле «до/после». Количество строк контекста задаётся параметром 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'] >>> for line in context_diff(s1, s2, fromfile='before.py', tofile='after.py'): ... sys.stdout.write(line) *** 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('apple', 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) и возвращает истину, если символ является мусором, и ложь в противном случае. По умолчанию – функция уровня модуля IS_CHARACTER_JUNK(), которая отфильтровывает пробельные символы (пробел или табуляцию; примечание: не стоит включать сюда символ новой строки!).
Tools/scripts/ndiff.py – это интерфейс командной строки для этой функции.
>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(1), ... 'ore\ntree\nemu\n'.splitlines(1)) >>> 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(1), ... 'ore\ntree\nemu\n'.splitlines(1)) >>> 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 равным "", чтобы выходные данные были полностью без символов новой строки.
Формат контекстного 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'] >>> for line in unified_diff(s1, s2, fromfile='before.py', tofile='after.py'): ... sys.stdout.write(line) --- before.py +++ after.py @@ -1,4 +1,4 @@ -bacon -eggs -ham +python +eggy +hamster guido
Смотрите Интерфейс командной строки для difflib для более подробного примера.
- 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 года.
6.3.1. Объекты SequenceMatcher¶SequenceMatcher Objects
Класс SequenceMatcher имеет следующий конструктор:
- class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)
Необязательный аргумент isjunk должен быть None (по умолчанию) или функцией с одним аргументом, которая принимает элемент последовательности и возвращает True тогда и только тогда, когда элемент является «мусором» и должен игнорироваться. Передача None в качестве isjunk эквивалентна передаче lambda x: 0; иными словами, ни один элемент не игнорируется. Например, передайте:
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, ahi, blo, bhi)¶
Найти самый длинный совпадающий блок в 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).
- 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(), чтобы получить верхнюю границу.
Три метода, возвращающие отношение совпадающих символов к общему количеству, могут давать разные результаты из-за разной степени приближения, хотя 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
6.3.2. Примеры 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]
См. также
- The get_close_matches() function in this module which shows how simple code building on SequenceMatcher can be used to do useful work.
- Простой рецепт управления версиями для небольшого приложения, построенного с помощью SequenceMatcher.
6.3.3. Объекты Differ¶Differ Objects
Обратите внимание, что дельты, созданные Differ, не претендуют на то, чтобы быть минимальными различиями. Напротив, минимальные различия часто неочевидны, потому что они синхронизируются везде, где возможно, иногда случайно совпадая на расстоянии 100 страниц. Ограничение точек синхронизации непрерывными совпадениями сохраняет некоторое понятие локальности, ценой получения более длинного diff.
Класс Differ имеет следующий конструктор:
- class difflib.Differ(linejunk=None, charjunk=None)
Необязательные именованные параметры linejunk и charjunk предназначены для функций фильтрации (или None):
linejunk: функция, которая принимает один строковый аргумент и возвращает True, если строка является мусором. По умолчанию None, то есть ни одна строка не считается мусором.
charjunk: функция, которая принимает один символьный аргумент (строку длины 1) и возвращает True, если символ является мусором. По умолчанию None, то есть ни один символ не считается мусором.
Объекты Differ используются (дельты создаются) с помощью одного метода:
- compare(a, b)¶
Сравнивает две последовательности строк и генерирует дельту (последовательность строк).
Каждая последовательность должна содержать отдельные однострочные строки, заканчивающиеся символами новой строки. Такие последовательности можно получить с помощью метода readlines() файлоподобных объектов. Созданная дельта также состоит из строк, завершающихся символом новой строки, готовых к печати как есть с помощью метода writelines() файлоподобного объекта.
6.3.4. Пример 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(1)
>>> 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(1)
Затем мы создаем экземпляр объекта Differ:
>>> d = Differ()
Note that when instantiating a Differ object we may pass functions to filter out line and character “junk.” See the Differ() constructor for details.
Наконец, мы сравниваем два:
>>> 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.
6.3.5. Интерфейс командной строки для difflib¶A command-line interface to difflib
В этом примере показано, как использовать difflib для создания утилиты, подобной diff. Она также содержится в дистрибутиве исходного кода Python как Tools/scripts/diff.py.
""" Интерфейс командной строки для difflib.py, выводящий разницу в четырёх форматах:
* ndiff: выводит каждую строку и подсвечивает изменения между строками.
* context: подсвечивает кластеры изменений в формате «до/после».
* unified: подсвечивает кластеры изменений во встроенном формате.
* html: генерирует построчное сравнение с подсветкой изменений.
"""
import sys, os, time, difflib, optparse
def main():
# Настройка парсера аргументов
usage = "usage: %prog [options] fromfile tofile"
parser = optparse.OptionParser(usage)
parser.add_option("-c", action="store_true", default=False,
help='Produce a context format diff (default)')
parser.add_option("-u", action="store_true", default=False,
help='Produce a unified format diff')
hlp = 'Produce HTML side by side diff (can use -c and -l in conjunction)'
parser.add_option("-m", action="store_true", default=False, help=hlp)
parser.add_option("-n", action="store_true", default=False,
help='Produce a ndiff format diff')
parser.add_option("-l", "--lines", type="int", default=3,
help='Set number of context lines (default 3)')
(options, args) = parser.parse_args()
if len(args) == 0:
parser.print_help()
sys.exit(1)
if len(args) != 2:
parser.error("need to specify both a fromfile and tofile")
n = options.lines
fromfile, tofile = args # в соответствии со строкой использования
# передаются в качестве аргументов функции diff
fromdate = time.ctime(os.stat(fromfile).st_mtime)
todate = time.ctime(os.stat(tofile).st_mtime)
with open(fromfile) as fromf, open(tofile) as tof:
fromlines, tolines = list(fromf), list(tof)
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)
# используется writelines, так как diff – генератор
sys.stdout.writelines(diff)
if __name__ == '__main__':
main()