difflib — игнорировать различия в пробелах с помощью ndiff()?

#python #diff #difflib

#python #разница #difflib

Вопрос:

Я просмотрел некоторые ответы на похожие вопросы здесь, но, думаю, я все еще чего-то не понимаю в том, как difflib.ndiff() работает?

Я смотрю на ndiff , в частности, потому, что документация подразумевает, что по умолчанию diff будет игнорировать изменения пробелов.

Вот простая программа, в которой я ожидаю, что строки в Differ (т. Е. возвращаемое значение из difflib.ndiff() ) будут пустыми:

 import difflib

# a simple set of lines
A_LINES = [
    'Line 1',
    'Line 2',
]

# should be same as A_LINES if whitespace is ignored
B_LINES = [
    '  Line 1',
    '  Line 2',
]

def test_2(a, b):
    # differ = difflib.ndiff(a, b)
    differ = difflib.ndiff(a, b, charjunk=difflib.IS_CHARACTER_JUNK)
    for line in differ:
        print(line)

def main(a_fn, b_fn):
    test_2(A_LINES, B_LINES)


if __name__ == '__main__':
    main()
  

difflib.IS_CHARACTER_JUNK() кажется, это просто предикат, который возвращает True на ' ' и 't' , False иначе. Вызываете ли вы ndiff() явным вызовом IS_CHARACTER_JUNK или принимаете значение по умолчанию и не упоминаете charjunk аргумент, я получаю тот же результат:

 - Line 1
    Line 1
?   

- Line 2
    Line 2
?   
  

Это не тот результат, который я ожидал бы для diff, который игнорирует пробелы.
Это кажется мне очень неожиданным, учитывая документацию для ndiff (см.:https://docs.python.org/3/library/difflib.html). Документация отключена, или странная, или неправильная, или я просто чего-то не понимаю?

Как бы я вызвал ndiff() такой, чтобы в генераторе ‘difference’ не было строк для этого примера?

Любая помощь, позволяющая лучше понять, как это сделать "ignore whitespace"-type diffs , очень ценится.

Комментарии:

1. концепция «мусора» в difflib плохо документирована (как и его алгоритм в целом), но «мусор» на самом деле не игнорируется.

2. Я не думаю, что у difflib есть какие-либо функции для игнорирования различий.

3. В чем разница между использованием ndiff, принимающим функцию по умолчанию, которая обрабатывает пробел и табуляцию как ненужные, и явным предоставлением ей функции, которая безоговорочно возвращала бы False, если различия не игнорируются? Я не понимаю, что значит рассматривать определенный символ как «мусор», если такие различия все еще не игнорируются?

4. @user2357112 поддерживает ndiff-файлы ndiff и SequenceMatcher object для поддержки нежелательной строки и символов. но это просто не работает. Похоже на ошибку: IS_CHARACTER_JUNK (поведение по умолчанию) не отфильтровывает то, что он сообщает, что делает (пробел и табуляция). Вопрос все еще остается в силе (python 3.6, не проверял, было ли это исправлено в более новых версиях)

Ответ №1:

Кажется, что IS_CHARACTER_JUNK функция фильтра вызывается, но не оказывает никакого влияния на фильтрацию нежелательных символов. Для меня это похоже на ошибку. Python 3.6 difflib по-прежнему ведет себя так же.

На данный момент я могу предложить приемлемый обходной путь: удалите конечные и начальные пробелы из строк и замените все повторяющиеся пробелы одним пробелом. Это обеспечивает, по крайней мере, эксплуатируемый вывод (удаление всех пробелов было бы непрактичным).

 import difflib
import re

lines1 = ["foo bar ","cat ","nope"]
lines2 = ["foo  bar   ","hello","cat    "]

def prefilter(line):
    return re.sub("s "," ",line.strip())

for d in difflib.ndiff([prefilter(x) for x in lines1],[prefilter(x) for x in lines2]):
    print(d)
  

результат (только добавленные / удаленные строки отображаются как изменения, строки с добавленными / удаленными пробелами — нет)

   foo bar
  hello
  cat
- nope
  

Ответ №2:

Из просмотра исходного кода наhttps://github.com/python/cpython/blob/3.10/Lib/difflib.py , я понимаю, что используются как linejunk, так и charjunk, но таким образом, что мусор символов не имеет никакого эффекта. Это кажется скорее недостатком в логике, чем ошибкой как таковой.

Краткий ответ: нет, невозможно вызвать ndiff() таким образом, чтобы в генераторе различий в вашем примере не было строк.

Способ, которым это работает, заключается в следующем:

ndiff() делегирует Difference().compare() и передает как linkjunk, так и charjunk в Difference, который их сохраняет.

Differ.compare сначала определяет различия с помощью SequenceMatcher, передавая только функцию linejunk.

Когда он печатает различия, он использует _fancy_replace для различий в одной строке, используя ‘^’. Это использует SequenceMatcher (снова), но на этот раз передает функцию charjunk.

Однако для полных различий в строке он просто печатает ‘ ‘ или ‘-‘, никогда больше не вызывая SequenceMatcher.

Итак, для полных различий строк charjunk никогда не используется.

Если _fancy_replace действительно игнорирует пробелы при печати различий в одной строке, мы никогда не узнаем, потому что, когда есть разница в пробелах, первый проход с SequenceMatcher сгенерирует различия в полной строке, и _fancy_replace никогда не вызывается.

Вкратце: первый вызов SequenceMatcher исключает использование charjunk при вызове SequenceMatcher во второй раз, потому что он сгенерирует разницу в строке (с и -) и без вычурной разницы.

Нет, в документации об этом ничего не ясно.

Я надеюсь, что это улучшает ваше понимание.

Я не нашел другого способа сделать то, что вы просили, используя difflib, кроме перезаписи больших его частей.