difflib.md
1> **Источник:** https://python-all.ru/3.12/library/difflib.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# `difflib` – Средства для вычисления различий89**Исходный код:** [Lib/difflib.py](https://python-all.ru/src/3.12/Lib/difflib.py)1011---1213Этот модуль предоставляет классы и функции для сравнения последовательностей. Он может использоваться, например, для сравнения файлов и может выдавать информацию о различиях файлов в различных форматах, включая HTML, контекстные и унифицированные различия. Для сравнения каталогов и файлов см. также модуль [`filecmp`](https://python-all.ru/3.12/library/filecmp.html#module-filecmp).1415#### `class difflib.SequenceMatcher`1617Это гибкий класс для сравнения пар последовательностей любого типа, при условии, что элементы последовательностей [хешируемы](https://python-all.ru/3.12/glossary.html#term-hashable). Базовый алгоритм предшествует и немного сложнее, чем алгоритм, опубликованный в конце 1980-х годов Рэтклиффом и Обершелпом под гиперболическим названием «gestalt pattern matching». Идея заключается в нахождении самой длинной непрерывной совпадающей подпоследовательности, не содержащей «мусорных» элементов; эти «мусорные» элементы – те, которые в некотором смысле неинтересны, например, пустые строки или пробелы. (Обработка мусора является расширением алгоритма Рэтклиффа и Обершелпа.) Та же идея затем рекурсивно применяется к частям последовательностей слева и справа от совпадающей подпоследовательности. Это не даёт минимальных последовательностей редактирования, но, как правило, даёт совпадения, которые «выглядят правильно» для человека.1819**Время выполнения:** Базовый алгоритм Рэтклиффа-Обершелпа имеет кубическое время в худшем случае и квадратичное время в ожидаемом. [`SequenceMatcher`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher) имеет квадратичное время для худшего случая, а поведение в ожидаемом случае сложным образом зависит от того, сколько элементов у последовательностей общих; время в лучшем случае линейно.2021**Автоматическая эвристика мусора:** [`SequenceMatcher`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher) поддерживает эвристику, которая автоматически помечает определённые элементы последовательности как мусор. Эвристика подсчитывает, сколько раз каждый отдельный элемент появляется в последовательности. Если дубликаты элемента (после первого) составляют более 1% последовательности и длина последовательности не менее 200 элементов, этот элемент помечается как «популярный» и считается мусором для целей сопоставления последовательностей. Эту эвристику можно отключить, установив аргумент `autojunk` в значение `False` при создании [`SequenceMatcher`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher).2223Изменено в версии 3.2: Добавлен параметр *autojunk*.2425#### `class difflib.Differ`2627Это класс для сравнения последовательностей строк текста и создания человекочитаемых различий или дельт. Differ использует [`SequenceMatcher`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher) как для сравнения последовательностей строк, так и для сравнения последовательностей символов внутри похожих (близко совпадающих) строк.2829Каждая строка дельты [`Differ`](https://python-all.ru/3.12/library/difflib.html#difflib.Differ) начинается с двухбуквенного кода:3031| Код | Значение |32| --- | --- |33| `'- '` | строка, уникальная для последовательности 1 |34| `'+ '` | строка, уникальная для последовательности 2 |35| `' '` | строка, общая для обеих последовательностей |36| `'? '` | строка, отсутствующая в каждой входной последовательности |3738Строки, начинающиеся с ‘`?`’, пытаются направить взгляд на различия внутри строк и не присутствуют ни в одной из входных последовательностей. Эти строки могут сбивать с толку, если последовательности содержат пробельные символы, такие как пробелы, табуляции или разрывы строк.3940#### `class difflib.HtmlDiff`4142Этот класс можно использовать для создания HTML-таблицы (или полного HTML-файла, содержащего таблицу), отображающей поочередное сравнение текста строка за строкой с подсветкой изменений между строками и внутри строк. Таблица может быть создана в режиме полных или контекстных различий.4344Конструктор этого класса:4546#### `__init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)`4748Инициализирует экземпляр [`HtmlDiff`](https://python-all.ru/3.12/library/difflib.html#difflib.HtmlDiff).4950*tabsize* – необязательный именованный аргумент для задания шага табуляции; по умолчанию равен `8`.5152*wrapcolumn* – необязательный именованный аргумент, задающий номер столбца, в котором строки разбиваются и переносятся; по умолчанию равен `None`, что означает отсутствие переноса строк.5354*linejunk* и *charjunk* – необязательные именованные аргументы, передаваемые в [`ndiff()`](https://python-all.ru/3.12/library/difflib.html#difflib.ndiff) (используется [`HtmlDiff`](https://python-all.ru/3.12/library/difflib.html#difflib.HtmlDiff) для создания поочередного HTML-сравнения). См. документацию [`ndiff()`](https://python-all.ru/3.12/library/difflib.html#difflib.ndiff) для получения значений по умолчанию и описаний аргументов.5556Следующие методы являются открытыми:5758#### `make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5, *, charset='utf-8')`5960Сравнивает *fromlines* и *tolines* (списки строк) и возвращает строку, содержащую полный HTML-файл с таблицей, отображающей построчные различия с подсветкой изменений между строками и внутри строк.6162*fromdesc* и *todesc* – необязательные именованные аргументы для задания строк заголовков столбцов исходного и целевого файлов (оба по умолчанию равны пустой строке).6364*context* и *numlines* – необязательные именованные аргументы. Установите *context* в `True`, чтобы показывать контекстные различия; в противном случае по умолчанию используется `False` для отображения полных файлов. *numlines* по умолчанию равен `5`. Когда *context* равен `True`, *numlines* управляет количеством строк контекста, окружающих подсветку различий. Когда *context* равен `False`, *numlines* управляет количеством строк, показываемых перед подсветкой различий при использовании гиперссылок «далее» (установка в ноль приведёт к тому, что гиперссылки «далее» будут размещать следующую подсветку различий в верхней части браузера без предшествующего контекста).6566> **Примечание**67>68> *fromdesc* и *todesc* интерпретируются как неэкранированный HTML и должны быть правильно экранированы при получении ввода из ненадёжных источников.6970Изменено в версии 3.5: *charset* добавлен аргумент, задаваемый только по ключевому слову. Кодировка по умолчанию HTML-документа изменилась с `'ISO-8859-1'` на `'utf-8'`.7172#### `make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)`7374Сравнивает *fromlines* и *tolines* (списки строк) и возвращает строку, которая представляет собой полную HTML-таблицу с построчным отображением различий, выделяя изменения между строками и внутри строк.7576Аргументы этого метода такие же, как у метода [`make_file()`](https://python-all.ru/3.12/library/difflib.html#difflib.HtmlDiff.make_file) .7778#### `difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')`7980Сравнивает *a* и *b* (списки строк); возвращает дельту ([генератор](https://python-all.ru/3.12/glossary.html#term-generator), порождающий строки дельты) в формате контекстного diff.8182Контекстные диффы – это компактный способ показать только изменённые строки плюс несколько строк контекста. Изменения отображаются в стиле «до/после». Количество строк контекста задаётся параметром *n*, по умолчанию – три.8384По умолчанию управляющие строки diff (те, что содержат `***` или `---`) создаются с завершающим переводом строки. Это удобно, так как входные данные, созданные с помощью [`io.IOBase.readlines()`](https://python-all.ru/3.12/library/io.html#io.IOBase.readlines), дают diff, подходящие для использования с [`io.IOBase.writelines()`](https://python-all.ru/3.12/library/io.html#io.IOBase.writelines), поскольку и входные, и выходные данные имеют завершающие переводы строк.8586Для входных данных, не имеющих завершающего перевода строки, установите аргумент *lineterm* равным `""`, чтобы выходные данные были полностью без символов новой строки.8788Формат контекстного diff обычно содержит заголовок с именами файлов и временем изменения. Любые или все из них могут быть заданы строками для *fromfile*, *tofile*, *fromfiledate* и *tofiledate*. Время изменения обычно указывается в формате ISO 8601. Если не указано, строки по умолчанию пусты.8990```python91>>> import sys92>>> from difflib import *93>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']94>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']95>>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py',96... tofile='after.py'))97*** before.py98--- after.py99***************100*** 1,4 ****101! bacon102! eggs103! ham104 guido105--- 1,4 ----106! python107! eggy108! hamster109 guido110```111112Смотрите [Интерфейс командной строки для difflib](https://python-all.ru/3.12/library/difflib.html#difflib-interface) для более подробного примера.113114#### `difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)`115116Возвращает список наилучших «достаточно хороших» совпадений. *word* – это последовательность, для которой требуются близкие совпадения (обычно строка), а *possibilities* – список последовательностей, с которыми нужно сопоставлять *word* (обычно список строк).117118Необязательный аргумент *n* (по умолчанию `3`) – максимальное количество близких совпадений, которые нужно вернуть; *n* должно быть больше `0`.119120Необязательный аргумент *cutoff* (по умолчанию `0.6`) – число с плавающей точкой в диапазоне \[0, 1\]. Варианты, которые набирают меньше этого порога сходства с *word*, игнорируются.121122Наилучшие (не более *n*) совпадения из возможных вариантов возвращаются в виде списка, отсортированного по мере сходства: сначала самые похожие.123124```python125>>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'])126['apple', 'ape']127>>> import keyword128>>> get_close_matches('wheel', keyword.kwlist)129['while']130>>> get_close_matches('pineapple', keyword.kwlist)131[]132>>> get_close_matches('accept', keyword.kwlist)133['except']134```135136#### `difflib.ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK)`137138Сравнивает *a* и *b* (списки строк); возвращает дельту в стиле [`Differ`](https://python-all.ru/3.12/library/difflib.html#difflib.Differ) ([генератор](https://python-all.ru/3.12/glossary.html#term-generator), порождающий строки дельты).139140Необязательные именованные параметры *linejunk* и *charjunk* – это функции фильтрации (или `None`):141142*linejunk*: функция, которая принимает один строковый аргумент и возвращает true, если строка является мусором, или false в противном случае. Значение по умолчанию – `None`. Также существует функция уровня модуля [`IS_LINE_JUNK()`](https://python-all.ru/3.12/library/difflib.html#difflib.IS_LINE_JUNK), которая отфильтровывает строки без видимых символов, за исключением не более одного символа решётки (`'#'`). Однако базовый класс [`SequenceMatcher`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher) выполняет динамический анализ того, какие строки встречаются настолько часто, что создают шум, и это обычно работает лучше, чем использование этой функции.143144*charjunk*: функция, которая принимает символ (строку длиной 1) и возвращает истину, если символ является мусором, и ложь в противном случае. По умолчанию – функция уровня модуля [`IS_CHARACTER_JUNK()`](https://python-all.ru/3.12/library/difflib.html#difflib.IS_CHARACTER_JUNK), которая отфильтровывает пробельные символы (пробел или табуляция; включать сюда перевод строки – плохая идея!).145146```python147>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),148... 'ore\ntree\nemu\n'.splitlines(keepends=True))149>>> print(''.join(diff), end="")150- one151? ^152+ ore153? ^154- two155- three156? -157+ tree158+ emu159```160161#### `difflib.restore(sequence, which)`162163Возвращает одну из двух последовательностей, породивших дельту.164165По заданной *sequence*, полученной с помощью [`Differ.compare()`](https://python-all.ru/3.12/library/difflib.html#difflib.Differ.compare) или [`ndiff()`](https://python-all.ru/3.12/library/difflib.html#difflib.ndiff), извлекает строки, относящиеся к файлу 1 или 2 (параметр *which*), удаляя префиксы строк.166167Пример:168169```python170>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),171... 'ore\ntree\nemu\n'.splitlines(keepends=True))172>>> diff = list(diff) # материализовать сгенерированную дельту в список173>>> print(''.join(restore(diff, 1)), end="")174one175two176three177>>> print(''.join(restore(diff, 2)), end="")178ore179tree180emu181```182183#### `difflib.unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')`184185Сравнивает *a* и *b* (списки строк); возвращает дельту ([генератор](https://python-all.ru/3.12/glossary.html#term-generator), порождающий строки дельты) в формате unified diff.186187Унифицированный дифф – это компактный способ показать только изменённые строки плюс несколько строк контекста. Изменения отображаются встроенным стилем (вместо отдельных блоков «до/после»). Количество строк контекста задаётся параметром *n*, который по умолчанию равен трём.188189По умолчанию управляющие строки diff (те, что содержат `---`, `+++` или `@@`) создаются с завершающим переводом строки. Это удобно, так как входные данные, созданные с помощью [`io.IOBase.readlines()`](https://python-all.ru/3.12/library/io.html#io.IOBase.readlines), дают diff, подходящие для использования с [`io.IOBase.writelines()`](https://python-all.ru/3.12/library/io.html#io.IOBase.writelines), поскольку и входные, и выходные данные имеют завершающие переводы строк.190191Для входных данных, не имеющих завершающего перевода строки, установите аргумент *lineterm* равным `""`, чтобы выходные данные были полностью без символов новой строки.192193Формат unified diff обычно содержит заголовок с именами файлов и временем изменения. Любые или все из них могут быть заданы строками для *fromfile*, *tofile*, *fromfiledate* и *tofiledate*. Время изменения обычно указывается в формате ISO 8601. Если не указано, строки по умолчанию пусты.194195```python196>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']197>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']198>>> sys.stdout.writelines(unified_diff(s1, s2, fromfile='before.py', tofile='after.py'))199--- before.py200+++ after.py201@@ -1,4 +1,4 @@202-bacon203-eggs204-ham205+python206+eggy207+hamster208 guido209```210211Смотрите [Интерфейс командной строки для difflib](https://python-all.ru/3.12/library/difflib.html#difflib-interface) для более подробного примера.212213#### `difflib.diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n')`214215Сравнивает *a* и *b* (списки объектов bytes) с помощью *dfunc*; возвращает последовательность строк дельты (тоже bytes) в формате, возвращаемом *dfunc*. *dfunc* должен быть вызываемым объектом, обычно [`unified_diff()`](https://python-all.ru/3.12/library/difflib.html#difflib.unified_diff) или [`context_diff()`](https://python-all.ru/3.12/library/difflib.html#difflib.context_diff).216217Позволяет сравнивать данные с неизвестной или несовместимой кодировкой. Все входные данные, кроме *n*, должны быть объектами bytes, а не str. Работает путём без потерь преобразования всех входных данных (кроме *n*) в str и вызова `dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)`. Затем вывод *dfunc* снова преобразуется в bytes, поэтому получаемые строки дельты имеют те же неизвестные или несовместимые кодировки, что и *a* и *b*.218219Добавлено в версии 3.5.220221#### `difflib.IS_LINE_JUNK(line)`222223Возвращает `True` для строк, которые можно игнорировать. Строка *line* считается игнорируемой, если *line* пуста или содержит только `'#'`, в противном случае она не игнорируется. Используется как значение по умолчанию для параметра *linejunk* в [`ndiff()`](https://python-all.ru/3.12/library/difflib.html#difflib.ndiff) в старых версиях.224225#### `difflib.IS_CHARACTER_JUNK(ch)`226227Возвращает `True` для символов, которые можно игнорировать. Символ *ch* считается игнорируемым, если *ch* является пробелом или табуляцией, в противном случае он не игнорируется. Используется как значение по умолчанию для параметра *charjunk* в [`ndiff()`](https://python-all.ru/3.12/library/difflib.html#difflib.ndiff).228229> **См. также**230>231> **[Сопоставление с образцом: гештальт-подход](https://python-all.ru/3.12/library/difflib.html)**232>233> Обсуждение похожего алгоритма Джона У. Рэтклиффа и Д. Э. Мецнера. Оно было опубликовано в [Dr. Dobb’s Journal](https://python-all.ru/3.12/library/difflib.html) в июле 1988 года.234235## Объекты SequenceMatcher236237Класс [`SequenceMatcher`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher) имеет следующий конструктор:238239#### `class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)`240241Необязательный аргумент *isjunk* должен быть `None` (значение по умолчанию) или функцией с одним аргументом, которая принимает элемент последовательности и возвращает true, если и только если элемент является «мусором» (junk) и его следует игнорировать. Передача `None` для *isjunk* эквивалентна передаче `lambda x: False`; иными словами, никакие элементы не игнорируются. Например, передайте:242243```python244lambda x: x in " \t"245```246247если строки сравниваются как последовательности символов и не требуется выравнивание по пробелам или табуляциям.248249Необязательные аргументы *a* и *b* – это последовательности для сравнения; обе по умолчанию равны пустым строкам. Элементы обеих последовательностей должны быть [хэшируемыми](https://python-all.ru/3.12/glossary.html#term-hashable) (hashable).250251Необязательный аргумент *autojunk* можно использовать для отключения автоматической эвристики мусора.252253Изменено в версии 3.2: Добавлен параметр *autojunk*.254255Объекты SequenceMatcher имеют три атрибута данных: *bjunk* – это множество элементов *b*, для которых *isjunk* равно `True`; *bpopular* – это множество не-мусорных элементов, считающихся популярными по эвристике (если она не отключена); *b2j* – это словарь, отображающий оставшиеся элементы *b* в список позиций, где они встречаются. Все три сбрасываются при каждом сбросе *b* с помощью [`set_seqs()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.set_seqs) или [`set_seq2()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.set_seq2).256257Добавлено в версии 3.2: Атрибуты *bjunk* и *bpopular*.258259Объекты [`SequenceMatcher`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher) имеют следующие методы:260261#### `set_seqs(a, b)`262263Устанавливает две последовательности для сравнения.264265[`SequenceMatcher`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher) вычисляет и кеширует подробную информацию о второй последовательности, поэтому если требуется сравнить одну последовательность с множеством других, используйте [`set_seq2()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.set_seq2) для однократной установки общей последовательности и вызывайте [`set_seq1()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.set_seq1) многократно, по одному разу для каждой из остальных последовательностей.266267#### `set_seq1(a)`268269Устанавливает первую последовательность для сравнения. Вторая последовательность для сравнения не изменяется.270271#### `set_seq2(b)`272273Устанавливает вторую последовательность для сравнения. Первая последовательность для сравнения не изменяется.274275#### `find_longest_match(alo=0, ahi=None, blo=0, bhi=None)`276277Находит самый длинный совпадающий блок в `a[alo:ahi]` и `b[blo:bhi]`.278279Если *isjunk* был опущен или равен `None`, [`find_longest_match()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.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*.280281```python282>>> s = SequenceMatcher(None, " abcd", "abcd abcd")283>>> s.find_longest_match(0, 5, 0, 9)284Match(a=0, b=4, size=5)285```286287Если *isjunk* был указан, сначала определяется самый длинный совпадающий блок, как описано выше, но с дополнительным ограничением: в блоке не должно быть мусорных элементов. Затем этот блок расширяется как можно дальше за счёт сопоставления (только) мусорных элементов с обеих сторон. Таким образом, результирующий блок никогда не совпадает по мусору, за исключением случаев, когда один и тот же мусор оказывается смежным с интересным совпадением.288289Вот тот же пример, что и ранее, но теперь пробелы считаются мусором. Это не позволяет `' abcd'` совпадать с `' abcd'` в хвосте второй последовательности напрямую. Вместо этого может совпасть только `'abcd'`, и он совпадает с самым левым `'abcd'` во второй последовательности:290291```python292>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")293>>> s.find_longest_match(0, 5, 0, 9)294Match(a=1, b=0, size=4)295```296297Если ни один блок не совпадает, возвращается `(alo, blo, 0)`.298299Этот метод возвращает [именованный кортеж](https://python-all.ru/3.12/glossary.html#term-named-tuple) `Match(a, b, size)`.300301Изменено в версии 3.9: Добавлены аргументы по умолчанию.302303#### `get_matching_blocks()`304305Возвращает список троек, описывающих непересекающиеся совпадающие подпоследовательности. Каждая тройка имеет вид `(i, j, n)` и означает, что `a[i:i+n] == b[j:j+n]`. Тройки монотонно возрастают по *i* и *j*.306307Последняя тройка фиктивная и имеет значение `(len(a), len(b), 0)`. Это единственная тройка с `n == 0`. Если `(i, j, n)` и `(i', j', n')` являются соседними тройками в списке, и вторая не является последней тройкой в списке, то `i+n < i'` или `j+n < j'`; другими словами, соседние тройки всегда описывают несмежные равные блоки.308309```pycon310>>> s = SequenceMatcher(None, "abxcd", "abcd")311>>> s.get_matching_blocks()312[Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]313```314315#### `get_opcodes()`316317Возвращает список 5-кортежей, описывающих, как преобразовать *a* в *b*. Каждый кортеж имеет вид `(tag, i1, i2, j1, j2)`. Первый кортеж содержит `i1 == j1 == 0`, а остальные кортежи имеют *i1* равным *i2* из предыдущего кортежа, и, аналогично, *j1* равным предыдущему *j2*.318319Значения *tag* – это строки со следующим смыслом:320321| Значение | Значение |322| --- | --- |323| `'replace'` | `a[i1:i2]` должно быть заменено на `b[j1:j2]`. |324| `'delete'` | `a[i1:i2]` должно быть удалено. Обратите внимание, что `j1 == j2` в этом случае. |325| `'insert'` | `b[j1:j2]` должно быть вставлено в `a[i1:i1]`. Обратите внимание, что `i1 == i2` в этом случае. |326| `'equal'` | `a[i1:i2] == b[j1:j2]` (подпоследовательности равны). |327328Например:329330```python331>>> a = "qabxcd"332>>> b = "abycdf"333>>> s = SequenceMatcher(None, a, b)334>>> for tag, i1, i2, j1, j2 in s.get_opcodes():335... print('{:7} a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(336... tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))337delete a[0:1] --> b[0:0] 'q' --> ''338equal a[1:3] --> b[0:2] 'ab' --> 'ab'339replace a[3:4] --> b[2:3] 'x' --> 'y'340equal a[4:6] --> b[3:5] 'cd' --> 'cd'341insert a[6:6] --> b[5:6] '' --> 'f'342```343344#### `get_grouped_opcodes(n=3)`345346Возвращает [генератор](https://python-all.ru/3.12/glossary.html#term-generator) групп с не более чем *n* строками контекста.347348Начиная с групп, возвращаемых [`get_opcodes()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.get_opcodes), этот метод выделяет более мелкие кластеры изменений и удаляет промежуточные диапазоны, в которых нет изменений.349350Группы возвращаются в том же формате, что и [`get_opcodes()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.get_opcodes).351352#### `ratio()`353354Возвращает меру схожести последовательностей в виде числа с плавающей запятой в диапазоне \[0, 1\].355356Где T – общее количество элементов в обеих последовательностях, а M – количество совпадений, это 2.0\*M / T. Обратите внимание, что это `1.0`, если последовательности идентичны, и `0.0`, если у них нет ничего общего.357358Это дорого вычислять, если [`get_matching_blocks()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.get_matching_blocks) или [`get_opcodes()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.get_opcodes) ещё не были вызваны; в этом случае, возможно, стоит сначала попробовать [`quick_ratio()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.quick_ratio) или [`real_quick_ratio()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.real_quick_ratio), чтобы получить верхнюю границу.359360> **Примечание**361>362> Внимание: результат вызова [`ratio()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.ratio) может зависеть от порядка аргументов. Например:363>364> ```python365> >>> SequenceMatcher(None, 'tide', 'diet').ratio()366> 0.25367> >>> SequenceMatcher(None, 'diet', 'tide').ratio()368> 0.5369> ```370371#### `quick_ratio()`372373Возвращает верхнюю границу для [`ratio()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.ratio) относительно быстро.374375#### `real_quick_ratio()`376377Возвращает верхнюю границу для [`ratio()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.ratio) очень быстро.378379Три метода, возвращающих отношение совпадающих символов к общему количеству, могут давать разные результаты из-за разного уровня приближения, хотя [`quick_ratio()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.quick_ratio) и [`real_quick_ratio()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.real_quick_ratio) всегда не меньше, чем [`ratio()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.ratio):380381```python382>>> s = SequenceMatcher(None, "abcd", "bcde")383>>> s.ratio()3840.75385>>> s.quick_ratio()3860.75387>>> s.real_quick_ratio()3881.0389```390391## Примеры SequenceMatcher392393В этом примере сравниваются две строки, при этом пробелы считаются «мусором»:394395```python396>>> s = SequenceMatcher(lambda x: x == " ",397... "private Thread currentThread;",398... "private volatile Thread currentThread;")399```400401[`ratio()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.ratio) возвращает число с плавающей запятой в \[0, 1\], измеряющее схожесть последовательностей. Эмпирически, значение [`ratio()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.ratio) больше 0,6 означает, что последовательности близки к совпадению:402403```python404>>> print(round(s.ratio(), 3))4050.866406```407408Если вас интересует только то, где последовательности совпадают, [`get_matching_blocks()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.get_matching_blocks) удобен:409410```python411>>> for block in s.get_matching_blocks():412... print("a[%d] and b[%d] match for %d elements" % block)413a[0] and b[0] match for 8 elements414a[8] and b[17] match for 21 elements415a[29] and b[38] match for 0 elements416```417418Обратите внимание, что последний кортеж, возвращаемый [`get_matching_blocks()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.get_matching_blocks), всегда фиктивный, `(len(a), len(b), 0)`, и это единственный случай, когда последний элемент кортежа (количество совпавших элементов) равен `0`.419420Если вы хотите узнать, как изменить первую последовательность на вторую, используйте [`get_opcodes()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.get_opcodes):421422```python423>>> for opcode in s.get_opcodes():424... print("%6s a[%d:%d] b[%d:%d]" % opcode)425 equal a[0:8] b[0:8]426insert a[8:8] b[8:17]427 equal a[8:29] b[17:38]428```429430> **См. также**431>432> - Функция [`get_close_matches()`](https://python-all.ru/3.12/library/difflib.html#difflib.get_close_matches) в этом модуле показывает, как простой код, построенный на [`SequenceMatcher`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher), может использоваться для полезной работы.433> - [Простой рецепт контроля версий](https://python-all.ru/3.12/library/difflib.html) для небольшого приложения, построенного с помощью [`SequenceMatcher`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher).434435## Объекты Differ436437Обратите внимание, что дельты, сгенерированные [`Differ`](https://python-all.ru/3.12/library/difflib.html#difflib.Differ), не претендуют на то, чтобы быть **минимальными** различиями. Напротив, минимальные различия часто противоречат интуиции, поскольку они синхронизируются везде, где возможно, иногда случайно сопоставляя фрагменты, отстоящие на 100 страниц. Ограничение точек синхронизации смежными совпадениями сохраняет некоторое понятие локальности, ценой иногда более длинного diff.438439Класс [`Differ`](https://python-all.ru/3.12/library/difflib.html#difflib.Differ) имеет следующий конструктор:440441#### `class difflib.Differ(linejunk=None, charjunk=None)`442443Необязательные именованные параметры *linejunk* и *charjunk* предназначены для функций фильтрации (или `None`):444445*linejunk*: функция, которая принимает один строковый аргумент и возвращает истину, если строка является мусорной. По умолчанию – `None`, то есть ни одна строка не считается мусорной.446447*charjunk*: функция, которая принимает один символьный аргумент (строку длиной 1) и возвращает истину, если символ является мусорным. По умолчанию – `None`, то есть ни один символ не считается мусорным.448449Эти функции фильтрации мусора ускоряют сопоставление для поиска различий и не приводят к игнорированию каких-либо различающихся строк или символов. Прочтите описание параметра *isjunk* метода [`find_longest_match()`](https://python-all.ru/3.12/library/difflib.html#difflib.SequenceMatcher.find_longest_match) для объяснения.450451Объекты [`Differ`](https://python-all.ru/3.12/library/difflib.html#difflib.Differ) используются (генерируют дельты) через единственный метод:452453#### `compare(a, b)`454455Сравнивает две последовательности строк и генерирует дельту (последовательность строк).456457Каждая последовательность должна содержать отдельные однострочные строки, заканчивающиеся символами новой строки. Такие последовательности можно получить из метода [`readlines()`](https://python-all.ru/3.12/library/io.html#io.IOBase.readlines) файлоподобных объектов. Сгенерированная дельта также состоит из строк, завершающихся новой строкой, готовых к печати как есть с помощью метода [`writelines()`](https://python-all.ru/3.12/library/io.html#io.IOBase.writelines) файлоподобного объекта.458459## Пример Differ460461Этот пример сравнивает два текста. Сначала мы задаем тексты – последовательности отдельных однострочных строк, заканчивающихся символами новой строки (такие последовательности также можно получить из метода [`readlines()`](https://python-all.ru/3.12/library/io.html#io.IOBase.readlines) файлоподобных объектов):462463```python464>>> text1 = ''' 1. Beautiful is better than ugly.465... 2. Explicit is better than implicit.466... 3. Simple is better than complex.467... 4. Complex is better than complicated.468... '''.splitlines(keepends=True)469>>> len(text1)4704471>>> text1[0][-1]472'\n'473>>> text2 = ''' 1. Beautiful is better than ugly.474... 3. Simple is better than complex.475... 4. Complicated is better than complex.476... 5. Flat is better than nested.477... '''.splitlines(keepends=True)478```479480Затем мы создаем экземпляр объекта Differ:481482```python483>>> d = Differ()484```485486Обратите внимание, что при создании экземпляра объекта [`Differ`](https://python-all.ru/3.12/library/difflib.html#difflib.Differ) можно передать функции для фильтрации «мусорных» строк и символов. Подробнее см. конструктор [`Differ()`](https://python-all.ru/3.12/library/difflib.html#difflib.Differ).487488Наконец, мы сравниваем два:489490```python491>>> result = list(d.compare(text1, text2))492```493494`result` – это список строк, поэтому давайте выведем его в удобочитаемом виде:495496```python497>>> from pprint import pprint498>>> pprint(result)499[' 1. Beautiful is better than ugly.\n',500 '- 2. Explicit is better than implicit.\n',501 '- 3. Simple is better than complex.\n',502 '+ 3. Simple is better than complex.\n',503 '? ++\n',504 '- 4. Complex is better than complicated.\n',505 '? ^ ---- ^\n',506 '+ 4. Complicated is better than complex.\n',507 '? ++++ ^ ^\n',508 '+ 5. Flat is better than nested.\n']509```510511В виде одной многострочной строки это выглядит так:512513```python514>>> import sys515>>> sys.stdout.writelines(result)516 1. Beautiful is better than ugly.517- 2. Explicit is better than implicit.518- 3. Simple is better than complex.519+ 3. Simple is better than complex.520? ++521- 4. Complex is better than complicated.522? ^ ---- ^523+ 4. Complicated is better than complex.524? ++++ ^ ^525+ 5. Flat is better than nested.526```527528## Интерфейс командной строки для difflib529530Этот пример показывает, как использовать difflib для создания утилиты, подобной `diff`.531532```python533""" Интерфейс командной строки для difflib.py, выводящий разницу в четырёх форматах:534535* ndiff: выводит каждую строку и подсвечивает изменения между строками.536* context: подсвечивает кластеры изменений в формате «до/после».537* unified: подсвечивает кластеры изменений во встроенном формате.538* html: генерирует построчное сравнение с подсветкой изменений.539540"""541542import sys, os, difflib, argparse543from datetime import datetime, timezone544545def file_mtime(path):546 t = datetime.fromtimestamp(os.stat(path).st_mtime,547 timezone.utc)548 return t.astimezone().isoformat()549550def main():551552 parser = argparse.ArgumentParser()553 parser.add_argument('-c', action='store_true', default=False,554 help='Produce a context format diff (default)')555 parser.add_argument('-u', action='store_true', default=False,556 help='Produce a unified format diff')557 parser.add_argument('-m', action='store_true', default=False,558 help='Produce HTML side by side diff '559 '(can use -c and -l in conjunction)')560 parser.add_argument('-n', action='store_true', default=False,561 help='Produce a ndiff format diff')562 parser.add_argument('-l', '--lines', type=int, default=3,563 help='Set number of context lines (default 3)')564 parser.add_argument('fromfile')565 parser.add_argument('tofile')566 options = parser.parse_args()567568 n = options.lines569 fromfile = options.fromfile570 tofile = options.tofile571572 fromdate = file_mtime(fromfile)573 todate = file_mtime(tofile)574 with open(fromfile) as ff:575 fromlines = ff.readlines()576 with open(tofile) as tf:577 tolines = tf.readlines()578579 if options.u:580 diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)581 elif options.n:582 diff = difflib.ndiff(fromlines, tolines)583 elif options.m:584 diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n)585 else:586 diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)587588 sys.stdout.writelines(diff)589590if __name__ == '__main__':591 main()592```593594## Пример ndiff595596Этот пример показывает, как использовать [`difflib.ndiff()`](https://python-all.ru/3.12/library/difflib.html#difflib.ndiff).597598```python599"""ndiff [-q] file1 file2600 или601ndiff (-r1 | -r2) < ndiff_output > file1_or_file2602603Выводит в stdout понятный человеку отчёт о различиях файлов. Отмечаются как меж-604строчные, так и внутристрочные различия. Во второй форме воссоздаёт file1605(-r1) или file2 (-r2) в stdout на основе отчёта ndiff со stdin.606607В первой форме, если не указан -q ("quiet"), первые две строки608вывода – это609610-: file1611+: file2612613Каждая следующая строка начинается с двухбуквенного кода:614615 "- " строка, уникальная для file1616 "+ " строка, уникальная для file2617 " " строка, общая для обоих файлов618 "? " строка, отсутствующая в обоих входных файлах619620Строки, начинающиеся с "? ", помогают глазу заметить внутристрочные621различия; в исходных файлах таких строк нет. Эти строки могут622сбивать с толку, если исходные файлы содержат символы табуляции.623624Первый файл можно восстановить, оставив только строки, начинающиеся с625" " " или "- ", и удалив эти двухсимвольные префиксы; используйте ndiff с -r1.626627Второй файл восстанавливается аналогично, но следует оставить только " " и628""+ "; используйте ndiff с -r2; или, в Unix, второй файл можно629восстановить, пропустив вывод через630631 sed -n '/^[+ ] /s/^..//p'632"""633634__version__ = 1, 7, 0635636import difflib, sys637638def fail(msg):639 out = sys.stderr.write640 out(msg + "\n\n")641 out(__doc__)642 return 0643644# открыть файл и вернуть файловый объект; ругать и вернуть 0, если не645# удалось открыть646def fopen(fname):647 try:648 return open(fname)649 except IOError as detail:650 return fail("couldn't open " + fname + ": " + str(detail))651652# открыть два файла и выдать diff в stdout; вернуть false при проблеме653def fcompare(f1name, f2name):654 f1 = fopen(f1name)655 f2 = fopen(f2name)656 if not f1 or not f2:657 return 0658659 a = f1.readlines(); f1.close()660 b = f2.readlines(); f2.close()661 for line in difflib.ndiff(a, b):662 print(line, end=' ')663664 return 1665666# разобрать args (обычно sys.argv[1:]) и сравнить;667# вернуть false при проблеме668669def main(args):670 import getopt671 try:672 opts, args = getopt.getopt(args, "qr:")673 except getopt.error as detail:674 return fail(str(detail))675 noisy = 1676 qseen = rseen = 0677 for opt, val in opts:678 if opt == "-q":679 qseen = 1680 noisy = 0681 elif opt == "-r":682 rseen = 1683 whichfile = val684 if qseen and rseen:685 return fail("can't specify both -q and -r")686 if rseen:687 if args:688 return fail("no args allowed with -r option")689 if whichfile in ("1", "2"):690 restore(whichfile)691 return 1692 return fail("-r value must be 1 or 2")693 if len(args) != 2:694 return fail("need 2 filename args")695 f1name, f2name = args696 if noisy:697 print('-:', f1name)698 print('+:', f2name)699 return fcompare(f1name, f2name)700701# прочитать вывод ndiff со stdin и вывести file1 (which=='1') или702# file2 (which=='2') в stdout703704def restore(which):705 restored = difflib.restore(sys.stdin.readlines(), which)706 sys.stdout.writelines(restored)707708if __name__ == '__main__':709 main(sys.argv[1:])710```711