Почему re.findall() находит больше совпадений, чем re.sub()?

Рассмотрим следующее:

>>> import re
>>> a = "first:second"
>>> re.findall("[^:]*", a)
['first', '', 'second', '']
>>> re.sub("[^:]*", r"(\g<0>)", a)
'(first):(second)'

re.sub() поведение имеет больше смысла, но я также понимаю поведение re.findall(). В конце концов, вы можете сопоставить пустую строку между first и :, которая состоит только из символов без двоеточия (ровно нуля из них), но почему не работает re.sub()?

Не должен ли результат последней команды быть (first)():(second)()?

Ответы

Ответ 1

Вы используете *, который позволяет пустые совпадения:

'first'   -> matched
':'       -> not in the character class but, as the pattern can be empty due 
             to the *, an empty string is matched -->''
'second'  -> matched
'$'       -> can contain an empty string before,
             an empty string is matched -->''

Цитата документации для re.findall():

Пустые совпадения включаются в результат, если они не касаются начала другого совпадения.

Причина, по которой вы не видите пустые совпадения в подвыборных результатах, объясняется в документации для re.sub():

Пустые совпадения для шаблона заменяются только тогда, когда они не смежны с предыдущим совпадением.

Попробуйте следующее:

re.sub('(?:Choucroute garnie)*', '#', 'ornithorynque') 

И теперь это:

print re.sub('(?:nithorynque)*', '#', 'ornithorynque')

Нет последовательных #

Ответ 2

По каким-то причинам алгоритмы для обработки пустых совпадений различны.

В случае findall он работает как (оптимизированная версия): для каждого возможного начального индекса 0 <= я <= len (a), если строка соответствует i, затем добавьте совпадение; и избегайте дублирования результатов с помощью этого правила: если соответствие длины m в i, не ищите следующее совпадение перед я + m. Причина, по которой ваш пример возвращает ['first', '', 'second', ''], заключается в том, что пустые совпадения найдены сразу после first и second, но не после двоеточия --- поскольку поиск соответствия, начинающегося с этой позиции, возвращает полную строку second.

В случае sub разница, как вы заметили, явно игнорирует совпадения длины 0, которые происходят сразу после другого совпадения. Хотя я понимаю, почему это может помочь избежать неожиданного поведения sub, я не уверен, почему существует эта разница (например, почему бы findall не использовать одно и то же правило).

Ответ 3

import re
a = "first:second:three"
print re.findall("[^:]*", a)

возвращает всю подстроку, соответствующую шаблону, здесь она дает

>>> 
['first', '', 'second', '', 'three', '']

sub() предназначен для подстановки и будет заменять самые левые неперекрывающиеся вхождения шаблона вашим замещающим. ex

import re
a = "first:second:three"
print re.sub("[^:]*", r"smile", a)

дает

>>> 
smile:smile:smile

Вы можете указать количество вхождений, которые будут заменены на 4-й аргумент, count: