difflib — Helpers for computing deltas¶
Вихідний код: Lib/difflib.py
This module provides classes and functions for comparing sequences. Most of them compare sequences of text lines (for example lists of strings, or file objects) and produce diffs – reports on the differences. Diffs can be produced in in various formats, including HTML and context and unified diffs – formats produced by tools like diff and git diff.
Comparisons are done using a matching algorithm implemented in
SequenceMatcher – a flexible class for comparing pairs of sequences
of any type, not just text, so long as the sequence elements are
hashable.
Junk heuristic¶
difflib uses a junk heuristic: some items are deemed to be
junk, and ignored when searching for similarities.
Ideally, these are uninteresting or common items, such as blank lines
or whitespace.
This heuristic can speed the algorithm up (because it reduces the number of possible combinations) and it can produce results that are more understandable for humans (typically breaking on whitespace). But it can also cause pathological cases:
Inappropriately chosen junk items can cause an unexpectedly large (but still correct) result.
The default heuristic is asymmetric: only the second sequence is inspected when determining what is considered junk, so comparing A to B can give different results than comparing B to A and reversing the result.
By default, if the second input sequence is at least 200 items long, items that account for more than 1% it are considered junk.
Depending on your data, you should consider turning this heuristic off
(setting SequenceMatcher’s autojunk argument to to False)
or tuning it (using the isjunk argument, perhaps to one of the
predefined functions).
The difflib algorithm¶
The algorithm used in SequenceMatcher predates, and is a little
fancier than, an algorithm published in the late 1980s by Ratcliff and
Obershelp under the hyperbolic name «gestalt pattern matching.»
The idea is to find the longest contiguous subsequence common to both inputs,
then recursively handle the pieces of the sequences to the left and to the
right of the matching subsequence.
Дивись також
- Pattern Matching: The Gestalt Approach
Discussion of a similar algorithm by John W. Ratcliff and D. E. Metzener. This was published in Dr. Dobb’s Journal in July, 1988.
As an extension to the Ratcliff and Obershelp algorithm, difflib
searches for the longest junk-free contiguous subsequence.
See the Junk heuristic section for details.
Деталі реалізації CPython: Timing
The basic Ratcliff-Obershelp algorithm is cubic time in the worst
case and quadratic time in the expected case.
difflib’s algorithm is quadratic time for the worst case and has
expected-case behavior dependent in a complicated way on how many elements
the sequences have in common;
best case time is linear.
Diff generation¶
- class difflib.Differ¶
Це клас для порівняння послідовностей рядків тексту та створення зрозумілих людині відмінностей або дельт. Differ використовує
SequenceMatcherяк для порівняння послідовностей рядків, так і для порівняння послідовностей символів у подібних (майже відповідних) рядках.Кожен рядок дельти
Differпочинається двобуквеним кодом:Код
Значення
'- ''рядок, унікальний для послідовності 1
'+ 'рядок, унікальний для послідовності 2
' 'лінія, спільна для обох послідовностей
'? ''рядок відсутній у жодній послідовності введення
Lines beginning with „
?“ attempt to guide the eye to intraline differences, and were not present in either input sequence. These lines can be confusing if the sequences contain whitespace characters, such as spaces, tabs or line breaks.Note that
Differ-generated deltas make no claim to be minimal diffs. To the contrary, minimal diffs are often counter-intuitive for humans, because they synch up anywhere possible, sometimes at accidental matches 100 pages apart. Restricting synch points to contiguous matches preserves some notion of locality, at the occasional cost of producing a longer diff.Клас
Differмає такий конструктор:- __init__(linejunk=None, charjunk=None)¶
Додаткові параметри ключових слів linejunk і charjunk призначені для функцій фільтра (або
None):linejunk: функція, яка приймає один рядковий аргумент і повертає істину, якщо рядок є небажаним. Типовим значенням є
None, що означає, що жоден рядок не вважається небажаним.charjunk: функція, яка приймає односимвольний аргумент (рядок довжиною 1) і повертає істину, якщо символ непотрібний. Типовим значенням є
None, що означає, що жоден символ не вважається небажаним.Ці функції фільтрації сміття пришвидшують зіставлення для пошуку відмінностей і не призводять до ігнорування будь-яких відмінних рядків чи символів. Щоб отримати пояснення, прочитайте опис параметра isjunk методу
find_longest_match().
Об’єкти
Differвикористовуються (генеруються дельти) за допомогою одного методу:- compare(a, b)¶
Порівняйте дві послідовності рядків і згенеруйте дельту (послідовність рядків).
Each sequence must contain individual single-line strings ending with newlines. Such sequences can be obtained from the
readlines()method of file-like objects. The generated delta also consists of newline-terminated strings, ready to be printed as-is via thewritelines()method of a file-like object.
- class difflib.HtmlDiff¶
Цей клас можна використовувати для створення HTML-таблиці (або повного HTML-файлу, що містить таблицю), що показує пліч-о-пліч, рядок за рядком, порівняння тексту з підсвічуванням міжрядкових і внутрішньорядкових змін. Таблицю можна створити в режимі повної різниці або в режимі контекстної різниці.
Попередження
The trailing newlines get stripped before the diff, so the result can be incomplete. See gh-71896 for details.
Конструктор для цього класу:
- __init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)¶
Ініціалізує екземпляр
HtmlDiff.tabsize є необов’язковим аргументом ключового слова для визначення інтервалу табуляції та за замовчуванням
8.wrapcolumn є необов’язковим ключовим словом для вказівки номера стовпця, де рядки розбиті та перенесені, за замовчуванням
None, якщо рядки не перенесені.linejunk і charjunk є необов’язковими аргументами ключових слів, які передаються в
ndiff()(використовуютьсяHtmlDiffдля генерування відмінностей у HTML). Перегляньте документаціюndiff(), щоб дізнатися про значення аргументів за замовчуванням та описи.
Публічними є такі методи:
- make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5, *, charset='utf-8')¶
Порівнює fromlines і tolines (списки рядків) і повертає рядок, який є повним HTML-файлом, що містить таблицю, у якій показано рядкові відмінності з виділеними міжрядковими та внутрішньорядковими змінами.
fromdesc і todesc є необов’язковими аргументами ключових слів для вказівки рядків заголовків стовпців файлу from/to (обидва за замовчуванням є порожнім рядком).
context і numlines є необов’язковими аргументами ключового слова. Встановіть для context значення
True, якщо мають відображатися контекстуальні відмінності, інакше значенням за замовчуванням єFalseдля відображення повних файлів. numlines за умовчанням має значення5. Якщо context має значенняTrue, numlines контролює кількість рядків контексту, які оточують виділення різниці. Коли context має значенняFalse, numlines контролює кількість рядків, які відображаються перед підсвічуванням різниці під час використання «наступних» гіперпосилань (встановлення нуля призведе до того, що «наступні» гіперпосилання розмістять наступне підсвічування різниці в у верхній частині браузера без будь-якого початкового контексту).Примітка
fromdesc і todesc інтерпретуються як неекранований HTML і мають бути належним чином екрановані під час отримання вхідних даних із ненадійних джерел.
Змінено в версії 3.5: Додано charset лише ключовий аргумент. Стандартний набір кодів HTML-документа змінено з
'ISO-8859-1'на'utf-8'.
- make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)¶
Порівнює fromlines і tolines (списки рядків) і повертає рядок, який є повною HTML-таблицею, що показує рядкові відмінності з виділеними міжрядковими та внутрішньорядковими змінами.
Аргументи для цього методу такі самі, як і для методу
make_file().
- difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')¶
Порівняти a і b (списки рядків); повертає дельту (generator, що генерує дельта-рядки) у форматі контекстної різниці.
Контекстні відмінності — це компактний спосіб показати лише рядки, які змінилися, плюс кілька рядків контексту. Зміни відображаються у стилі до/після. Кількість рядків контексту встановлюється n, яке за замовчуванням дорівнює трьом.
За замовчуванням рядки керування відмінностями (ті, що мають
***або---) створюються з кінцевим символом нового рядка. Це корисно, щоб вхідні дані, створені зio.IOBase.readlines(), призводили до відмінностей, які придатні для використання зio.IOBase.writelines(), оскільки і вхідні, і вихідні дані мають кінцеві символи нового рядка.Для вхідних даних, які не мають кінцевих символів нового рядка, встановіть для аргументу lineterm значення
"", щоб вихідні дані не містили символів нового рядка.Формат контекстної різниці зазвичай має заголовок для імен файлів і часу модифікації. Будь-який або всі з них можна вказати за допомогою рядків для fromfile, tofile, fromfiledate і tofiledate. Час модифікації зазвичай виражається у форматі ISO 8601. Якщо не вказано, рядки за умовчанням пусті.
>>> import sys >>> from difflib import * >>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n'] >>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n'] >>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py', ... tofile='after.py')) *** before.py --- after.py *************** *** 1,4 **** ! bacon ! eggs ! ham guido --- 1,4 ---- ! python ! eggy ! hamster guido
Дивіться Інтерфейс командного рядка для difflib для більш детального прикладу.
- difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)¶
Поверніть список найкращих «досить хороших» збігів. слово — це послідовність, для якої потрібні близькі збіги (зазвичай це рядок), а можливості — це список послідовностей, з якими потрібно зіставити слово (зазвичай список рядків).
Необов’язковий аргумент n (за замовчуванням
3) — це максимальна кількість близьких збігів для повернення; n має бути більше, ніж0.Необов’язковий аргумент cutoff (за замовчуванням
0,6) є числом з плаваючою точкою в діапазоні [0, 1]. Можливості, які не схожі на слово, ігноруються.Найкращі (не більше ніж n) збіги серед можливих повертаються у списку, відсортованому за показником подібності, найбільш схожі першими.
>>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy']) ['apple', 'ape'] >>> import keyword >>> get_close_matches('wheel', keyword.kwlist) ['while'] >>> get_close_matches('pineapple', keyword.kwlist) [] >>> get_close_matches('accept', keyword.kwlist) ['except']
- difflib.ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK)¶
Порівняти a і b (списки рядків); повертає
Differ-стиль дельти (generator, який генерує дельта-лінії).Додаткові параметри ключових слів linejunk і charjunk є функціями фільтрації (або
None):linejunk: A function that accepts a single string argument, and returns true if the string is junk, or false if not. The default is
None. There is also a module-level functionIS_LINE_JUNK(), which filters out lines without visible characters, except for at most one hash character ('#') – however the underlyingSequenceMatcherclass does a dynamic analysis of which lines are so frequent as to constitute noise, and this usually works better than using this function.charjunk: функція, яка приймає символ (рядок довжиною 1) і повертає, якщо символ непотрібний, або false, якщо ні. За замовчуванням використовується функція рівня модуля
IS_CHARACTER_JUNK(), яка відфільтровує пробіли (пробіл або символ табуляції; це погана ідея включати новий рядок у це!).>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True), ... 'ore\ntree\nemu\n'.splitlines(keepends=True)) >>> print(''.join(diff), end="") - one ? ^ + ore ? ^ - two - three ? - + tree + emu
- difflib.restore(sequence, which)¶
Повертає одну з двох послідовностей, які створили дельту.
За наявності послідовності, створеної
Differ.compare()абоndiff(), витягти рядки, що походять із файлу 1 або 2 (параметр which), видаливши префікси рядків.приклад:
>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True), ... 'ore\ntree\nemu\n'.splitlines(keepends=True)) >>> diff = list(diff) # materialize the generated delta into a list >>> print(''.join(restore(diff, 1)), end="") one two three >>> print(''.join(restore(diff, 2)), end="") ore tree emu
- difflib.unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')¶
Порівняти a і b (списки рядків); повертає дельту (generator, що генерує дельта-лінії) в уніфікованому форматі різниці.
Уніфіковані відмінності — це компактний спосіб показати лише рядки, які змінилися, плюс кілька рядків контексту. Зміни відображаються у вбудованому стилі (замість окремих блоків до/після). Кількість рядків контексту встановлюється n, яке за замовчуванням дорівнює трьом.
За замовчуванням рядки керування відмінностями (з
---,+++або@@) створюються з кінцевим символом нового рядка. Це корисно, щоб вхідні дані, створені зio.IOBase.readlines(), призводили до відмінностей, які придатні для використання зio.IOBase.writelines(), оскільки і вхідні, і вихідні дані мають кінцеві символи нового рядка.Для вхідних даних, які не мають кінцевих символів нового рядка, встановіть для аргументу lineterm значення
"", щоб вихідні дані не містили символів нового рядка.The unified diff format normally has a header for filenames and modification times. Any or all of these may be specified using strings for fromfile, tofile, fromfiledate, and tofiledate. The modification times are normally expressed in the ISO 8601 format. If not specified, the strings default to blanks.
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n'] >>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n'] >>> sys.stdout.writelines(unified_diff(s1, s2, fromfile='before.py', tofile='after.py')) --- before.py +++ after.py @@ -1,4 +1,4 @@ -bacon -eggs -ham +python +eggy +hamster guido
Дивіться Інтерфейс командного рядка для difflib для більш детального прикладу.
- difflib.diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n')¶
Порівняти a і b (списки об’єктів bytes) за допомогою dfunc; дає послідовність дельта-рядків (також байтів) у форматі, який повертає dfunc. dfunc має бути викликом, як правило,
unified_diff()абоcontext_diff().Дозволяє порівнювати дані з невідомим або суперечливим кодуванням. Усі вхідні дані, крім n, мають бути об’єктами bytes, а не str. Працює без втрат, перетворюючи всі вхідні дані (крім n) на str і викликаючи
dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm). Вихідні дані dfunc потім перетворюються назад у байти, тому дельта-рядки, які ви отримуєте, мають таке ж невідоме/непослідовне кодування, що й a і b.Added in version 3.5.
Junk definition functions¶
SequenceMatcher objects¶
- class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)¶
Необов’язковий аргумент isjunk має бути
None(за замовчуванням) або функція з одним аргументом, яка приймає елемент послідовності та повертає true тоді і тільки тоді, коли елемент є «сміттєвим» і його слід ігнорувати. ПередачаNoneдля isjunk еквівалентна передачіlambda x: False; іншими словами, жоден елемент не ігнорується. Наприклад, pass:lambda x: x in " \t"
якщо ви порівнюєте рядки як послідовності символів і не хочете синхронізувати пробіли чи жорсткі табуляції.
Необов’язкові аргументи a і b є послідовностями для порівняння; обидва за замовчуванням порожні рядки. Елементи обох послідовностей мають бути hashable.
Необов’язковий аргумент autojunk можна використовувати для вимкнення автоматичної евристики сміття.
Змінено в версії 3.2: Added the autojunk parameter.
Об’єкти SequenceMatcher отримують три атрибути даних: bjunk — це набір елементів b, для яких isjunk має значення
True; bpopular — це набір непотрібних елементів, які вважаються популярними за евристикою (якщо її не вимкнено); b2j — це dict, що відображає решту елементів b на список позицій, де вони зустрічаються. Усі три скидаються щоразу, коли b скидається за допомогоюset_seqs()абоset_seq2().Added in version 3.2: Атрибути bjunk і bpopular.
Об’єкти
SequenceMatcherмають такі методи:- set_seqs(a, b)¶
Встановіть дві послідовності для порівняння.
SequenceMatcherобчислює та кешує детальну інформацію про другу послідовність, тому, якщо ви хочете порівняти одну послідовність із багатьма послідовностями, використовуйтеset_seq2(), щоб один раз установити типову послідовність і викликатиset_seq1()повторно, один раз для кожної з інших послідовностей.- set_seq1(a)¶
Встановіть першу послідовність для порівняння. Друга послідовність для порівняння не змінюється.
- set_seq2(b)¶
Встановіть другу послідовність для порівняння. Перша послідовність для порівняння не змінюється.
- find_longest_match(alo=0, ahi=None, blo=0, bhi=None)¶
Знайти найдовший відповідний блок у
a[alo:ahi]іb[blo:bhi].Якщо isjunk пропущено або
None,find_longest_match()повертає(i, j, k)так, щоa[i:i+k]дорівнюєb[j:j+k], деalo <= i <= i+k <= ahiandblo <= j <= j+k <= bhi. For all(i', j', k')meeting those conditions, the additional conditionsk > = 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).Цей метод повертає named tuple
Match(a, b, size).Змінено в версії 3.9: Додано типові аргументи.
- get_matching_blocks()¶
Повернути список трійок, що описують відповідні підпослідовності, що не перекриваються. Кожна трійка має форму
(i, j, n)і означає, щоa[i:i+n] == b[j:j+n]. Трійки монотонно зростають у i та j.Остання трійка є фіктивною і має значення
(len(a), len(b), 0). Це єдина трійка зn == 0. Якщо(i, j, n)і(i', j', n')є суміжними трійками в списку, а друга не є останньою трійкою в списку, тодіi +n < i'абоj+n < j'; іншими словами, сусідні трійки завжди описують несуміжні рівні блоки.>>> s = SequenceMatcher(None, "abxcd", "abcd") >>> s.get_matching_blocks() [Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]
- get_opcodes()¶
Повернути список із 5-ти кортежів, які описують, як перетворити a на b. Кожен кортеж має форму
(tag, i1, i2, j1, j2). Перший кортеж маєi1 == j1 == 0, а решта кортежів мають i1, що дорівнює i2 з попереднього кортежу, і, так само, j1 дорівнює попередньому j2.Значення тегу є рядками з такими значеннями:
Значення
Значення
'замінити'a[i1:i2]слід замінити наb[j1:j2].'видалити'a[i1:i2]слід видалити. Зверніть увагу, що в цьому випадкуj1 == j2.'вставити'b[j1:j2]слід вставити вa[i1:i1]. Зверніть увагу, що в цьому випадкуi1 == i2.'рівний''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)¶
Повертає generator груп із до 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(), щоб отримати верхня межа.
The three methods that return the ratio of matching to total characters can give
different results due to differing levels of approximation, although
quick_ratio() and real_quick_ratio()
are always at least as large as ratio():
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0
Examples¶
SequenceMatcher examples¶
У цьому прикладі порівнюються два рядки, вважаючи пробіли «сміттєвими»:
>>> s = SequenceMatcher(lambda x: x == " ",
... "private Thread currentThread;",
... "private volatile Thread currentThread;")
ratio() returns a float in [0, 1], measuring the similarity of the
sequences. As a rule of thumb, a ratio() value over 0.6 means the
sequences are close matches:
>>> print(round(s.ratio(), 3))
0.866
If you’re only interested in where the sequences match,
get_matching_blocks() is handy:
>>> 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
Note that the last tuple returned by get_matching_blocks()
is always a dummy, (len(a), len(b), 0), and this is the only case in which the last
tuple element (number of elements matched) is 0.
If you want to know how to change the first sequence into the second, use
get_opcodes():
>>> for opcode in s.get_opcodes():
... print("%6s a[%d:%d] b[%d:%d]" % opcode)
equal a[0:8] b[0:8]
insert a[8:8] b[8:17]
equal a[8:29] b[17:38]
Дивись також
Функція
get_close_matches()у цьому модулі, яка показує, як простий код, створений наSequenceMatcher, можна використовувати для виконання корисної роботи.Simple version control recipe for a small application built with
SequenceMatcher.
Differ example¶
This example compares two texts. First we set up the texts, sequences of
individual single-line strings ending with newlines (such sequences can also be
obtained from the readlines() method of file-like objects):
>>> text1 = ''' 1. Beautiful is better than ugly.
... 2. Explicit is better than implicit.
... 3. Simple is better than complex.
... 4. Complex is better than complicated.
... '''.splitlines(keepends=True)
>>> len(text1)
4
>>> text1[0][-1]
'\n'
>>> text2 = ''' 1. Beautiful is better than ugly.
... 3. Simple is better than complex.
... 4. Complicated is better than complex.
... 5. Flat is better than nested.
... '''.splitlines(keepends=True)
Далі ми створюємо екземпляр об’єкта Differ:
>>> d = Differ()
Зауважте, що під час створення екземпляра об’єкта Differ ми можемо передати функції для фільтрації «сміття» рядків і символів. Подробиці див. у конструкторі Differ().
Нарешті, ми порівнюємо два:
>>> result = list(d.compare(text1, text2))
результат - це список рядків, тож давайте красиво надрукуємо його:
>>> from pprint import pprint
>>> pprint(result)
[' 1. Beautiful is better than ugly.\n',
'- 2. Explicit is better than implicit.\n',
'- 3. Simple is better than complex.\n',
'+ 3. Simple is better than complex.\n',
'? ++\n',
'- 4. Complex is better than complicated.\n',
'? ^ ---- ^\n',
'+ 4. Complicated is better than complex.\n',
'? ++++ ^ ^\n',
'+ 5. Flat is better than nested.\n']
Як один багаторядковий рядок це виглядає так:
>>> import sys
>>> sys.stdout.writelines(result)
1. Beautiful is better than ugly.
- 2. Explicit is better than implicit.
- 3. Simple is better than complex.
+ 3. Simple is better than complex.
? ++
- 4. Complex is better than complicated.
? ^ ---- ^
+ 4. Complicated is better than complex.
? ++++ ^ ^
+ 5. Flat is better than nested.
Інтерфейс командного рядка для difflib¶
This example shows how to use difflib to create a diff-like utility.
""" Command-line interface to difflib.py providing diffs in four formats:
* ndiff: lists every line and highlights interline changes.
* context: highlights clusters of changes in a before/after format.
* unified: highlights clusters of changes in an inline format.
* html: generates side by side comparison with change highlights.
"""
import sys, os, difflib, argparse
import datetime as dt
def file_mtime(path):
t = dt.datetime.fromtimestamp(os.stat(path).st_mtime,
dt.timezone.utc)
return t.astimezone().isoformat()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-c', action='store_true', default=False,
help='Produce a context format diff (default)')
parser.add_argument('-u', action='store_true', default=False,
help='Produce a unified format diff')
parser.add_argument('-m', action='store_true', default=False,
help='Produce HTML side by side diff '
'(can use -c and -l in conjunction)')
parser.add_argument('-n', action='store_true', default=False,
help='Produce a ndiff format diff')
parser.add_argument('-l', '--lines', type=int, default=3,
help='Set number of context lines (default 3)')
parser.add_argument('fromfile')
parser.add_argument('tofile')
options = parser.parse_args()
n = options.lines
fromfile = options.fromfile
tofile = options.tofile
fromdate = file_mtime(fromfile)
todate = file_mtime(tofile)
with open(fromfile) as ff:
fromlines = ff.readlines()
with open(tofile) as tf:
tolines = tf.readlines()
if options.u:
diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
elif options.n:
diff = difflib.ndiff(fromlines, tolines)
elif options.m:
diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n)
else:
diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
sys.stdout.writelines(diff)
if __name__ == '__main__':
main()
ndiff example¶
This example shows how to use difflib.ndiff().
"""ndiff [-q] file1 file2
or
ndiff (-r1 | -r2) < ndiff_output > file1_or_file2
Print a human-friendly file difference report to stdout. Both inter-
and intra-line differences are noted. In the second form, recreate file1
(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.
In the first form, if -q ("quiet") is not specified, the first two lines
of output are
-: file1
+: file2
Each remaining line begins with a two-letter code:
"- " line unique to file1
"+ " line unique to file2
" " line common to both files
"? " line not present in either input file
Lines beginning with "? " attempt to guide the eye to intraline
differences, and were not present in either input file. These lines can be
confusing if the source files contain tab characters.
The first file can be recovered by retaining only lines that begin with
" " or "- ", and deleting those 2-character prefixes; use ndiff with -r1.
The second file can be recovered similarly, but by retaining only " " and
"+ " lines; use ndiff with -r2; or, on Unix, the second file can be
recovered by piping the output through
sed -n '/^[+ ] /s/^..//p'
"""
__version__ = 1, 7, 0
import difflib, sys
def fail(msg):
out = sys.stderr.write
out(msg + "\n\n")
out(__doc__)
return 0
# open a file & return the file object; gripe and return 0 if it
# couldn't be opened
def fopen(fname):
try:
return open(fname)
except IOError as detail:
return fail("couldn't open " + fname + ": " + str(detail))
# open two files & spray the diff to stdout; return false iff a problem
def fcompare(f1name, f2name):
f1 = fopen(f1name)
f2 = fopen(f2name)
if not f1 or not f2:
return 0
a = f1.readlines(); f1.close()
b = f2.readlines(); f2.close()
for line in difflib.ndiff(a, b):
print(line, end=' ')
return 1
# crack args (sys.argv[1:] is normal) & compare;
# return false iff a problem
def main(args):
import getopt
try:
opts, args = getopt.getopt(args, "qr:")
except getopt.error as detail:
return fail(str(detail))
noisy = 1
qseen = rseen = 0
for opt, val in opts:
if opt == "-q":
qseen = 1
noisy = 0
elif opt == "-r":
rseen = 1
whichfile = val
if qseen and rseen:
return fail("can't specify both -q and -r")
if rseen:
if args:
return fail("no args allowed with -r option")
if whichfile in ("1", "2"):
restore(whichfile)
return 1
return fail("-r value must be 1 or 2")
if len(args) != 2:
return fail("need 2 filename args")
f1name, f2name = args
if noisy:
print('-:', f1name)
print('+:', f2name)
return fcompare(f1name, f2name)
# read ndiff output from stdin, and print file1 (which=='1') or
# file2 (which=='2') to stdout
def restore(which):
restored = difflib.restore(sys.stdin.readlines(), which)
sys.stdout.writelines(restored)
if __name__ == '__main__':
main(sys.argv[1:])