difflib.md
1> **Источник:** https://python-all.ru/3.4/library/difflib.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 6.3. [`difflib`](https://python-all.ru/3.4/library/difflib.html#module-difflib) – Вспомогательные средства для вычисления дельт89Этот модуль предоставляет классы и функции для сравнения последовательностей. Его можно использовать, например, для сравнения файлов, и он может выводить информацию о различиях в различных форматах, включая HTML, контекстные и объединённые диффы. Для сравнения каталогов и файлов смотрите также модуль [`filecmp`](https://python-all.ru/3.4/library/filecmp.html#module-filecmp).1011#### `class difflib.SequenceMatcher`1213Это гибкий класс для сравнения пар последовательностей любого типа, при условии, что элементы последовательностей являются [*хэшируемыми*](https://python-all.ru/3.4/glossary.html#term-hashable). Базовый алгоритм предшествует алгоритму, опубликованному в конце 1980-х годов Рэтклиффом и Обершелпом под громким названием «гештальт-сопоставление шаблонов», и немного сложнее его. Идея заключается в поиске самой длинной непрерывной совпадающей подпоследовательности, не содержащей «мусорных» элементов (алгоритм Рэтклиффа и Обершелпа не учитывает мусор). Затем эта же идея рекурсивно применяется к частям последовательностей слева и справа от совпадающей подпоследовательности. Это не даёт минимальных последовательностей редактирования, но обычно даёт совпадения, которые «выглядят правильно» для человека.1415**Время выполнения:** Базовый алгоритм Рэтклиффа-Обершелпа имеет кубическую сложность в худшем случае и квадратичную в среднем. [`SequenceMatcher`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher) имеет квадратичную сложность в худшем случае, а поведение в среднем зависит сложным образом от того, сколько элементов у последовательностей общих; в лучшем случае сложность линейная.1617**Автоматическая эвристика мусора:** [`SequenceMatcher`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher) поддерживает эвристику, которая автоматически помечает некоторые элементы последовательности как мусор. Эвристика подсчитывает, сколько раз каждый отдельный элемент встречается в последовательности. Если дубликаты элемента (после первого) составляют более 1% последовательности и длина последовательности не менее 200 элементов, этот элемент помечается как «популярный» и считается мусором для целей сопоставления последовательностей. Эту эвристику можно отключить, установив аргумент `autojunk` в значение `False` при создании [`SequenceMatcher`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher).1819Новое в версии 3.2: Параметр *autojunk*.2021#### `class difflib.Differ`2223Это класс для сравнения последовательностей текстовых строк и создания различий или дельт, понятных человеку. Differ использует [`SequenceMatcher`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher) как для сравнения последовательностей строк, так и для сравнения последовательностей символов внутри похожих (почти совпадающих) строк.2425Каждая строка дельты, созданной [`Differ`](https://python-all.ru/3.4/library/difflib.html#difflib.Differ), начинается с двухбуквенного кода:2627| Код | Значение |28| --- | --- |29| `'- '` | строка, уникальная для последовательности 1 |30| `'+ '` | строка, уникальная для последовательности 2 |31| `' '` | строка, общая для обеих последовательностей |32| `'? '` | строка, отсутствующая в каждой входной последовательности |3334Строки, начинающиеся с ‘`?`‘, пытаются привлечь внимание к внутристрочным различиям и отсутствовали в обеих входных последовательностях. Эти строки могут сбивать с толку, если последовательности содержат символы табуляции.3536#### `class difflib.HtmlDiff`3738Этот класс можно использовать для создания HTML-таблицы (или полного HTML-файла, содержащего таблицу), отображающей поочередное сравнение текста строка за строкой с подсветкой изменений между строками и внутри строк. Таблица может быть создана в режиме полных или контекстных различий.3940Конструктор этого класса:4142#### `__init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)`4344Инициализирует экземпляр [`HtmlDiff`](https://python-all.ru/3.4/library/difflib.html#difflib.HtmlDiff).4546*tabsize* – необязательный именованный аргумент для задания шага табуляции; по умолчанию равен `8`.4748*wrapcolumn* – необязательный именованный аргумент, задающий номер столбца, в котором строки разбиваются и переносятся; по умолчанию `None`, то есть строки не переносятся.4950*linejunk* и *charjunk* – необязательные именованные аргументы, передаваемые в [`ndiff()`](https://python-all.ru/3.4/library/difflib.html#difflib.ndiff) (используется [`HtmlDiff`](https://python-all.ru/3.4/library/difflib.html#difflib.HtmlDiff) для генерации HTML-различий рядом). Смотрите документацию [`ndiff()`](https://python-all.ru/3.4/library/difflib.html#difflib.ndiff) для значений по умолчанию и описания аргументов.5152Следующие методы являются открытыми:5354#### `make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)`5556Сравнивает *fromlines* и *tolines* (списки строк) и возвращает строку, содержащую полный HTML-файл с таблицей, отображающей построчные различия с подсветкой изменений между строками и внутри строк.5758*fromdesc* и *todesc* – необязательные именованные аргументы для задания строк заголовков столбцов исходного и целевого файлов (оба по умолчанию равны пустой строке).5960*context* и *numlines* – необязательные именованные аргументы. Установите *context* в значение `True`, чтобы показывать контекстные различия, иначе по умолчанию `False`, показывающее полные файлы. *numlines* по умолчанию равен `5`. Когда *context* равно `True`, *numlines* управляет количеством строк контекста, окружающих подсветку различий. Когда *context* равно `False`, *numlines* управляет количеством строк, показываемых перед подсветкой различий при использовании гиперссылок «далее» (установка нуля приведёт к тому, что гиперссылка «далее» будет помещать следующую подсветку различий в верхнюю часть браузера без предшествующего контекста).6162#### `make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)`6364Сравнивает *fromlines* и *tolines* (списки строк) и возвращает строку, которая представляет собой полную HTML-таблицу с построчным отображением различий, выделяя изменения между строками и внутри строк.6566Аргументы этого метода такие же, как у метода [`make_file()`](https://python-all.ru/3.4/library/difflib.html#difflib.HtmlDiff.make_file).6768`Tools/scripts/diff.py` – это интерфейс командной строки для этого класса, содержащий хороший пример его использования.6970#### `difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')`7172Сравнивает *a* и *b* (списки строк); возвращает дельту ([*генератор*](https://python-all.ru/3.4/glossary.html#term-generator), порождающий строки дельты) в формате контекстного diff.7374Контекстные диффы – это компактный способ показать только изменённые строки плюс несколько строк контекста. Изменения отображаются в стиле «до/после». Количество строк контекста задаётся параметром *n*, по умолчанию – три.7576По умолчанию управляющие строки diff (те, которые содержат `***` или `---`) создаются с завершающим символом новой строки. Это полезно, так как входные данные, созданные с помощью [`io.IOBase.readlines()`](https://python-all.ru/3.4/library/io.html#io.IOBase.readlines), дают diff, подходящие для использования с [`io.IOBase.writelines()`](https://python-all.ru/3.4/library/io.html#io.IOBase.writelines), поскольку и входные, и выходные данные имеют завершающие символы новой строки.7778Для входных данных, не имеющих завершающего перевода строки, установите аргумент *lineterm* равным `""`, чтобы выходные данные были полностью без символов новой строки.7980Формат контекстного diff обычно содержит заголовок с именами файлов и временем изменения. Любые или все из них могут быть заданы строками для *fromfile*, *tofile*, *fromfiledate* и *tofiledate*. Время изменения обычно указывается в формате ISO 8601. Если не указано, строки по умолчанию пусты.8182```python83>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']84>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']85>>> for line in context_diff(s1, s2, fromfile='before.py', tofile='after.py'):86... sys.stdout.write(line) 87*** before.py88--- after.py89***************90*** 1,4 ****91! bacon92! eggs93! ham94 guido95--- 1,4 ----96! python97! eggy98! hamster99 guido100```101102Смотрите [*Интерфейс командной строки для difflib*](https://python-all.ru/3.4/library/difflib.html#difflib-interface) для более подробного примера.103104#### `difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)`105106Возвращает список наилучших «достаточно хороших» совпадений. *word* – это последовательность, для которой требуются близкие совпадения (обычно строка), а *possibilities* – список последовательностей, с которыми нужно сопоставлять *word* (обычно список строк).107108Необязательный аргумент *n* (по умолчанию `3`) – максимальное количество близких совпадений, которые нужно вернуть; *n* должно быть больше `0`.109110Необязательный аргумент *cutoff* (по умолчанию `0.6`) – число с плавающей точкой в диапазоне \[0, 1\]. Варианты, которые набирают меньше этого порога сходства с *word*, игнорируются.111112Наилучшие (не более *n*) совпадения из возможных вариантов возвращаются в виде списка, отсортированного по мере сходства: сначала самые похожие.113114```python115>>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'])116['apple', 'ape']117>>> import keyword118>>> get_close_matches('wheel', keyword.kwlist)119['while']120>>> get_close_matches('apple', keyword.kwlist)121[]122>>> get_close_matches('accept', keyword.kwlist)123['except']124```125126#### `difflib.ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK)`127128Сравнивает *a* и *b* (списки строк); возвращает дельту в стиле [`Differ`](https://python-all.ru/3.4/library/difflib.html#difflib.Differ) ([*генератор*](https://python-all.ru/3.4/glossary.html#term-generator), порождающий строки дельты).129130Необязательные именованные параметры *linejunk* и *charjunk* предназначены для функций-фильтров (или `None`):131132*linejunk*: Функция, которая принимает один строковый аргумент и возвращает true, если строка является мусором, и false в противном случае. По умолчанию `None`. Также существует функция уровня модуля [`IS_LINE_JUNK()`](https://python-all.ru/3.4/library/difflib.html#difflib.IS_LINE_JUNK), которая отфильтровывает строки без видимых символов, за исключением не более одного символа решётки (`'#'`) – однако базовый класс [`SequenceMatcher`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher) выполняет динамический анализ того, какие строки настолько часты, что составляют шум, и это обычно работает лучше, чем использование этой функции.133134*charjunk*: Функция, которая принимает символ (строку длины 1) и возвращает истину, если символ является мусором, и ложь в противном случае. По умолчанию – функция уровня модуля [`IS_CHARACTER_JUNK()`](https://python-all.ru/3.4/library/difflib.html#difflib.IS_CHARACTER_JUNK), которая отфильтровывает пробельные символы (пробел или табуляцию; примечание: не стоит включать сюда символ новой строки!).135136`Tools/scripts/ndiff.py` – это интерфейс командной строки для этой функции.137138```python139>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),140... 'ore\ntree\nemu\n'.splitlines(keepends=True))141>>> print(''.join(diff), end="")142- one143? ^144+ ore145? ^146- two147- three148? -149+ tree150+ emu151```152153#### `difflib.restore(sequence, which)`154155Возвращает одну из двух последовательностей, породивших дельту.156157Принимает *sequence*, созданный с помощью [`Differ.compare()`](https://python-all.ru/3.4/library/difflib.html#difflib.Differ.compare) или [`ndiff()`](https://python-all.ru/3.4/library/difflib.html#difflib.ndiff), извлекает строки, относящиеся к файлу 1 или 2 (параметр *which*), удаляя префиксы строк.158159Пример:160161```python162>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),163... 'ore\ntree\nemu\n'.splitlines(keepends=True))164>>> diff = list(diff) # материализовать сгенерированную дельту в список165>>> print(''.join(restore(diff, 1)), end="")166one167two168three169>>> print(''.join(restore(diff, 2)), end="")170ore171tree172emu173```174175#### `difflib.unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')`176177Сравнивает *a* и *b* (списки строк); возвращает дельту ([*генератор*](https://python-all.ru/3.4/glossary.html#term-generator), порождающий строки дельты) в формате unified diff.178179Унифицированный дифф – это компактный способ показать только изменённые строки плюс несколько строк контекста. Изменения отображаются встроенным стилем (вместо отдельных блоков «до/после»). Количество строк контекста задаётся параметром *n*, который по умолчанию равен трём.180181По умолчанию строки управления diff (те, что с `---`, `+++` или `@@`) создаются с завершающим переводом строки. Это полезно, потому что входные данные, полученные из [`io.IOBase.readlines()`](https://python-all.ru/3.4/library/io.html#io.IOBase.readlines), дают diff, подходящий для использования с [`io.IOBase.writelines()`](https://python-all.ru/3.4/library/io.html#io.IOBase.writelines), поскольку и входные, и выходные данные имеют завершающие переводы строк.182183Для входных данных, не имеющих завершающего перевода строки, установите аргумент *lineterm* равным `""`, чтобы выходные данные были полностью без символов новой строки.184185Формат контекстного diff обычно содержит заголовок с именами файлов и временем изменения. Любые или все из них могут быть заданы строками для *fromfile*, *tofile*, *fromfiledate* и *tofiledate*. Время изменения обычно указывается в формате ISO 8601. Если не указано, строки по умолчанию пусты.186187```python188>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']189>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']190>>> for line in unified_diff(s1, s2, fromfile='before.py', tofile='after.py'):191... sys.stdout.write(line) 192--- before.py193+++ after.py194@@ -1,4 +1,4 @@195-bacon196-eggs197-ham198+python199+eggy200+hamster201 guido202```203204Смотрите [*Интерфейс командной строки для difflib*](https://python-all.ru/3.4/library/difflib.html#difflib-interface) для более подробного примера.205206#### `difflib.IS_LINE_JUNK(line)`207208Возвращает True для строк, которые можно игнорировать. Строка *line* игнорируется, если *line* пуста или содержит только `'#'`, иначе не игнорируется. В старых версиях используется как значение по умолчанию для параметра *linejunk* в [`ndiff()`](https://python-all.ru/3.4/library/difflib.html#difflib.ndiff).209210#### `difflib.IS_CHARACTER_JUNK(ch)`211212Возвращает True для игнорируемых символов. Символ *ch* игнорируется, если *ch* является пробелом или табуляцией, иначе не игнорируется. Используется как значение по умолчанию для параметра *charjunk* в [`ndiff()`](https://python-all.ru/3.4/library/difflib.html#difflib.ndiff).213214> **См. также**215>216> **[Сопоставление с образцом: гештальт-подход](https://python-all.ru/3.4/library/difflib.html)**217>218> Обсуждение похожего алгоритма Джона У. Рэтклиффа и Д. Э. Мецнера. Оно было опубликовано в219>220> [Dr. Dobb’s Journal](https://python-all.ru/3.4/library/difflib.html)221>222> в июле 1988 года.223224## 6.3.1. Объекты SequenceMatcher225226Класс [`SequenceMatcher`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher) имеет следующий конструктор:227228#### `class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)`229230Необязательный аргумент *isjunk* должен быть `None` (по умолчанию) или функцией с одним аргументом, которая принимает элемент последовательности и возвращает True тогда и только тогда, когда элемент является «мусором» и должен игнорироваться. Передача `None` в качестве *isjunk* эквивалентна передаче `lambda x: 0`; иными словами, ни один элемент не игнорируется. Например, передайте:231232```python233lambda x: x in " \t"234```235236если строки сравниваются как последовательности символов и не требуется выравнивание по пробелам или табуляциям.237238Необязательные аргументы *a* и *b* – это последовательности для сравнения; обе по умолчанию равны пустым строкам. Элементы обеих последовательностей должны быть [*хэшируемыми*](https://python-all.ru/3.4/glossary.html#term-hashable) (hashable).239240Необязательный аргумент *autojunk* можно использовать для отключения автоматической эвристики мусора.241242Новое в версии 3.2: Параметр *autojunk*.243244Объекты SequenceMatcher имеют три атрибута данных: *bjunk* – множество элементов *b*, для которых *isjunk* равно `True`; *bpopular* – множество не-мусорных элементов, которые эвристика считает популярными (если она не отключена); *b2j* – словарь, отображающий остальные элементы *b* в список позиций, где они встречаются. Все три сбрасываются при каждом сбросе *b* с помощью [`set_seqs()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.set_seqs) или [`set_seq2()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.set_seq2).245246Новое в версии 3.2: Атрибуты *bjunk* и *bpopular*.247248Объекты [`SequenceMatcher`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher) имеют следующие методы:249250#### `set_seqs(a, b)`251252Устанавливает две последовательности для сравнения.253254[`SequenceMatcher`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher) вычисляет и кеширует подробную информацию о второй последовательности, поэтому, если требуется сравнить одну последовательность с несколькими, используйте [`set_seq2()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.set_seq2) для однократного задания общей последовательности и затем многократно вызывайте [`set_seq1()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.set_seq1) – по одному разу для каждой из остальных последовательностей.255256#### `set_seq1(a)`257258Устанавливает первую последовательность для сравнения. Вторая последовательность для сравнения не изменяется.259260#### `set_seq2(b)`261262Устанавливает вторую последовательность для сравнения. Первая последовательность для сравнения не изменяется.263264#### `find_longest_match(alo, ahi, blo, bhi)`265266Найти самый длинный совпадающий блок в `a[alo:ahi]` и `b[blo:bhi]`.267268Если *isjunk* был опущен или равен `None`, [`find_longest_match()`](https://python-all.ru/3.4/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*.269270```python271>>> s = SequenceMatcher(None, " abcd", "abcd abcd")272>>> s.find_longest_match(0, 5, 0, 9)273Match(a=0, b=4, size=5)274```275276Если *isjunk* был указан, сначала определяется самый длинный совпадающий блок, как описано выше, но с дополнительным ограничением: в блоке не должно быть мусорных элементов. Затем этот блок расширяется как можно дальше за счёт сопоставления (только) мусорных элементов с обеих сторон. Таким образом, результирующий блок никогда не совпадает по мусору, за исключением случаев, когда один и тот же мусор оказывается смежным с интересным совпадением.277278Вот тот же пример, что и раньше, но с учётом пробелов как мусора. Это не позволяет `' abcd'` напрямую совпасть с `' abcd'` в конце второй последовательности. Вместо этого совпасть может только `'abcd'`, и он совпадает с самым левым `'abcd'` во второй последовательности:279280```python281>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")282>>> s.find_longest_match(0, 5, 0, 9)283Match(a=1, b=0, size=4)284```285286Если ни один блок не совпадает, возвращается `(alo, blo, 0)`.287288Этот метод возвращает [*именованный кортеж*](https://python-all.ru/3.4/glossary.html#term-named-tuple) `Match(a, b, size)`.289290#### `get_matching_blocks()`291292Возвращает список троек, описывающих совпадающие подпоследовательности. Каждая тройка имеет вид `(i, j, n)` и означает, что `a[i:i+n] == b[j:j+n]`. Тройки монотонно возрастают по *i* и *j*.293294Последняя тройка – фиктивная, её значение `(len(a), len(b), 0)`. Это единственная тройка, у которой `n == 0`. Если `(i, j, n)` и `(i', j', n')` являются соседними тройками в списке, и вторая не является последней тройкой в списке, то `i+n != i'` или `j+n != j'`; иными словами, соседние тройки всегда описывают несмежные равные блоки.295296```python297>>> s = SequenceMatcher(None, "abxcd", "abcd")298>>> s.get_matching_blocks()299[Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]300```301302#### `get_opcodes()`303304Возвращает список 5-кортежей, описывающих, как преобразовать *a* в *b*. Каждый кортеж имеет вид `(tag, i1, i2, j1, j2)`. Первый кортеж имеет `i1 == j1 == 0`, а остальные кортежи имеют *i1*, равный *i2* из предыдущего кортежа, и, аналогично, *j1* равно предыдущему *j2*.305306Значения *tag* – это строки со следующим смыслом:307308| Значение | Значение |309| --- | --- |310| `'replace'` | `a[i1:i2]` должен быть заменён на `b[j1:j2]`. |311| `'delete'` | `a[i1:i2]` должен быть удалён. Обратите внимание, что в этом случае `j1 == j2`. |312| `'insert'` | `b[j1:j2]` должен быть вставлен в `a[i1:i1]`. Обратите внимание, что в этом случае `i1 == i2`. |313| `'equal'` | `a[i1:i2] == b[j1:j2]` (подпоследовательности равны). |314315Например:316317> ```python318> >>> a = "qabxcd"319> >>> b = "abycdf"320> >>> s = SequenceMatcher(None, a, b)321> >>> for tag, i1, i2, j1, j2 in s.get_opcodes():322> print('{:7} a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(323> tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))324> ```325>326> 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'327328#### `get_grouped_opcodes(n=3)`329330Возвращает [*генератор*](https://python-all.ru/3.4/glossary.html#term-generator) групп с не более чем *n* строками контекста.331332Начиная с групп, возвращаемых [`get_opcodes()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.get_opcodes), этот метод разбивает их на более мелкие кластеры изменений и удаляет промежуточные диапазоны, в которых нет изменений.333334Группы возвращаются в том же формате, что и [`get_opcodes()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.get_opcodes).335336#### `ratio()`337338Возвращает меру схожести последовательностей в виде числа с плавающей запятой в диапазоне \[0, 1\].339340Где T – общее количество элементов в обеих последовательностях, а M – количество совпадений, это 2.0\*M / T. Обратите внимание, что это `1.0`, если последовательности идентичны, и `0.0`, если у них нет ничего общего.341342Это дорогостоящая операция, если [`get_matching_blocks()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.get_matching_blocks) или [`get_opcodes()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.get_opcodes) ещё не были вызваны; в таком случае, возможно, стоит сначала попробовать [`quick_ratio()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.quick_ratio) или [`real_quick_ratio()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.real_quick_ratio), чтобы получить верхнюю границу.343344#### `quick_ratio()`345346Возвращает верхнюю границу [`ratio()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.ratio) относительно быстро.347348#### `real_quick_ratio()`349350Возвращает верхнюю границу [`ratio()`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher.ratio) очень быстро.351352Три метода, возвращающие отношение совпадающих символов к общему количеству, могут давать разные результаты из-за разной степени приближения, хотя `quick_ratio()` и `real_quick_ratio()` всегда не меньше, чем `ratio()`:353354```python355>>> s = SequenceMatcher(None, "abcd", "bcde")356>>> s.ratio()3570.75358>>> s.quick_ratio()3590.75360>>> s.real_quick_ratio()3611.0362```363364## 6.3.2. Примеры SequenceMatcher365366В этом примере сравниваются две строки, при этом пробелы считаются «мусором»:367368```python369>>> s = SequenceMatcher(lambda x: x == " ",370... "private Thread currentThread;",371... "private volatile Thread currentThread;")372```373374`ratio()` возвращает число с плавающей запятой в диапазоне \[0, 1\], измеряющее схожесть последовательностей. Эмпирически, значение `ratio()` больше 0,6 означает, что последовательности близки:375376```python377>>> print(round(s.ratio(), 3))3780.866379```380381Если интересует только, где последовательности совпадают, `get_matching_blocks()` будет удобен:382383```python384>>> for block in s.get_matching_blocks():385... print("a[%d] and b[%d] match for %d elements" % block)386a[0] and b[0] match for 8 elements387a[8] and b[17] match for 21 elements388a[29] and b[38] match for 0 elements389```390391Обратите внимание, что последний кортеж, возвращаемый `get_matching_blocks()`, всегда является фиктивным: `(len(a), len(b), 0)`, и это единственный случай, когда последний элемент кортежа (количество совпавших элементов) равен `0`.392393Если требуется узнать, как изменить первую последовательность, чтобы получить вторую, используйте `get_opcodes()`:394395```python396>>> for opcode in s.get_opcodes():397... print("%6s a[%d:%d] b[%d:%d]" % opcode)398 equal a[0:8] b[0:8]399insert a[8:8] b[8:17]400 equal a[8:29] b[17:38]401```402403> **См. также**404>405> - The [`get_close_matches()`](https://python-all.ru/3.4/library/difflib.html#difflib.get_close_matches) function in this module which shows how simple code building on [`SequenceMatcher`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher) can be used to do useful work.406> - [Простой рецепт управления версиями](https://python-all.ru/3.4/library/difflib.html) для небольшого приложения, построенного с помощью [`SequenceMatcher`](https://python-all.ru/3.4/library/difflib.html#difflib.SequenceMatcher).407408## 6.3.3. Объекты Differ409410Обратите внимание, что дельты, созданные [`Differ`](https://python-all.ru/3.4/library/difflib.html#difflib.Differ), не претендуют на то, чтобы быть **минимальными** различиями. Напротив, минимальные различия часто неочевидны, потому что они синхронизируются везде, где возможно, иногда случайно совпадая на расстоянии 100 страниц. Ограничение точек синхронизации непрерывными совпадениями сохраняет некоторое понятие локальности, ценой получения более длинного diff.411412Класс [`Differ`](https://python-all.ru/3.4/library/difflib.html#difflib.Differ) имеет следующий конструктор:413414#### `class difflib.Differ(linejunk=None, charjunk=None)`415416Необязательные именованные параметры *linejunk* и *charjunk* предназначены для функций фильтрации (или `None`):417418*linejunk*: функция, которая принимает один строковый аргумент и возвращает True, если строка является мусором. По умолчанию `None`, то есть ни одна строка не считается мусором.419420*charjunk*: функция, которая принимает один символьный аргумент (строку длины 1) и возвращает True, если символ является мусором. По умолчанию `None`, то есть ни один символ не считается мусором.421422Объекты [`Differ`](https://python-all.ru/3.4/library/difflib.html#difflib.Differ) используются (дельты создаются) с помощью одного метода:423424#### `compare(a, b)`425426Сравнивает две последовательности строк и генерирует дельту (последовательность строк).427428Каждая последовательность должна содержать отдельные однострочные строки, заканчивающиеся символами новой строки. Такие последовательности можно получить с помощью метода [`readlines()`](https://python-all.ru/3.4/library/io.html#io.IOBase.readlines) файлоподобных объектов. Созданная дельта также состоит из строк, завершающихся символом новой строки, готовых к печати как есть с помощью метода [`writelines()`](https://python-all.ru/3.4/library/io.html#io.IOBase.writelines) файлоподобного объекта.429430## 6.3.4. Пример Differ431432В этом примере сравниваются два текста. Сначала настроим тексты – последовательности отдельных однострочных строк, заканчивающихся символами новой строки (такие последовательности также можно получить из метода `readlines()` файлоподобных объектов):433434```python435>>> text1 = ''' 1. Beautiful is better than ugly.436... 2. Explicit is better than implicit.437... 3. Simple is better than complex.438... 4. Complex is better than complicated.439... '''.splitlines(keepends=True)440>>> len(text1)4414442>>> text1[0][-1]443'\n'444>>> text2 = ''' 1. Beautiful is better than ugly.445... 3. Simple is better than complex.446... 4. Complicated is better than complex.447... 5. Flat is better than nested.448... '''.splitlines(keepends=True)449```450451Затем мы создаем экземпляр объекта Differ:452453```python454>>> d = Differ()455```456457Note that when instantiating a [`Differ`](https://python-all.ru/3.4/library/difflib.html#difflib.Differ) object we may pass functions to filter out line and character “junk.” See the [`Differ()`](https://python-all.ru/3.4/library/difflib.html#difflib.Differ) constructor for details.458459Наконец, мы сравниваем два:460461```python462>>> result = list(d.compare(text1, text2))463```464465`result` – это список строк, поэтому выведем его в отформатированном виде:466467```python468>>> from pprint import pprint469>>> pprint(result)470[' 1. Beautiful is better than ugly.\n',471 '- 2. Explicit is better than implicit.\n',472 '- 3. Simple is better than complex.\n',473 '+ 3. Simple is better than complex.\n',474 '? ++\n',475 '- 4. Complex is better than complicated.\n',476 '? ^ ---- ^\n',477 '+ 4. Complicated is better than complex.\n',478 '? ++++ ^ ^\n',479 '+ 5. Flat is better than nested.\n']480```481482В виде одной многострочной строки это выглядит так:483484```python485>>> import sys486>>> sys.stdout.writelines(result)487 1. Beautiful is better than ugly.488- 2. Explicit is better than implicit.489- 3. Simple is better than complex.490+ 3. Simple is better than complex.491? ++492- 4. Complex is better than complicated.493? ^ ---- ^494+ 4. Complicated is better than complex.495? ++++ ^ ^496+ 5. Flat is better than nested.497```498499## 6.3.5. Интерфейс командной строки для difflib500501В этом примере показано, как использовать difflib для создания утилиты, подобной `diff`. Она также содержится в дистрибутиве исходного кода Python как `Tools/scripts/diff.py`.502503```python504""" Интерфейс командной строки для difflib.py, выводящий разницу в четырёх форматах:505506* ndiff: выводит каждую строку и подсвечивает изменения между строками.507* context: подсвечивает кластеры изменений в формате «до/после».508* unified: подсвечивает кластеры изменений во встроенном формате.509* html: генерирует построчное сравнение с подсветкой изменений.510511"""512513import sys, os, time, difflib, optparse514515def main():516 # Настройка парсера аргументов517 usage = "usage: %prog [options] fromfile tofile"518 parser = optparse.OptionParser(usage)519 parser.add_option("-c", action="store_true", default=False,520 help='Produce a context format diff (default)')521 parser.add_option("-u", action="store_true", default=False,522 help='Produce a unified format diff')523 hlp = 'Produce HTML side by side diff (can use -c and -l in conjunction)'524 parser.add_option("-m", action="store_true", default=False, help=hlp)525 parser.add_option("-n", action="store_true", default=False,526 help='Produce a ndiff format diff')527 parser.add_option("-l", "--lines", type="int", default=3,528 help='Set number of context lines (default 3)')529 (options, args) = parser.parse_args()530531 if len(args) == 0:532 parser.print_help()533 sys.exit(1)534 if len(args) != 2:535 parser.error("need to specify both a fromfile and tofile")536537 n = options.lines538 fromfile, tofile = args # в соответствии со строкой использования539540 # передаются в качестве аргументов функции diff541 fromdate = time.ctime(os.stat(fromfile).st_mtime)542 todate = time.ctime(os.stat(tofile).st_mtime)543 with open(fromfile) as fromf, open(tofile) as tof:544 fromlines, tolines = list(fromf), list(tof)545546 if options.u:547 diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile,548 fromdate, todate, n=n)549 elif options.n:550 diff = difflib.ndiff(fromlines, tolines)551 elif options.m:552 diff = difflib.HtmlDiff().make_file(fromlines, tolines, fromfile,553 tofile, context=options.c,554 numlines=n)555 else:556 diff = difflib.context_diff(fromlines, tolines, fromfile, tofile,557 fromdate, todate, n=n)558559 # используется writelines, так как diff – генератор560 sys.stdout.writelines(diff)561562if __name__ == '__main__':563 main()564```565