Модуль регулярных выражений Python против re - несоответствие паттернов
Обновление: эта проблема была решена разработчиком в commit be893e9
.
Если вы столкнулись с той же проблемой, обновите модуль regex
.
Вам нужна версия 2017.04.23
или выше.
Как указано в этом ответе
Мне нужно это регулярное выражение:
(?i)\b((\w{1,3})(-|\.{2,10})[\t ]?)+(\2\w{2,})
тоже работаю с модулем regex
...
import re # standard library
import regex # https://pypi.python.org/pypi/regex/
content = '"Erm....yes. T..T...Thank you for that."'
pattern = r"(?i)\b((\w{1,3})(-|\.{2,10})[\t ]?)+(\2\w{2,})"
substitute = r"\2-\4"
print(re.sub(pattern, substitute, content))
print(regex.sub(pattern, substitute, content))
Выход:
"Erm....yes. T-Thank you for that."
"-yes. T..T...Thank you for that."
В: Как мне написать это регулярное выражение, чтобы модуль regex
реагировал на него так же, как модуль re
?
Использование модуля re
не вариант, так как мне требуются оглядки с динамической длины.
Для пояснения: было бы неплохо, если бы регулярное выражение работало с обоими модулями, но, в конце концов, оно мне нужно только для regex
Ответы
Ответ 1
Кажется, что эта ошибка связана с возврatom. Это происходит, когда группа захвата повторяется, и группа захвата совпадает, а шаблон после группы - нет.
Пример:
>>> regex.sub(r'(?:(\d{1,3})x)+', r'\1', '123x5')
'5'
Для справки ожидаемый результат будет:
>>> re.sub(r'(?:(\d{1,3})x)+', r'\1', '123x5')
'1235'
На первой итерации группа захвата (\d{1,3})
использует первые 3 цифры, а x
использует следующий символ "x". Затем, из-за +
, попытка сопоставления повторяется 2 раза. На этот раз (\d{1,3})
соответствует "5", но x
не соответствует. Однако теперь значение группы захвата (пере) установлено на пустую строку вместо ожидаемого 123
.
В качестве обходного пути мы можем предотвратить сопоставление группы захвата. В этом случае достаточно изменить его на (\d{2,3})
, чтобы обойти ошибку (потому что она больше не соответствует "5"):
>>> regex.sub(r'(?:(\d{2,3})x)+', r'\1', '123x5')
'1235'
Что касается рассматриваемого паттерна, то мы можем использовать утверждение "с нетерпением"; мы меняем (\w{1,3})
на (?=\w{1,3}(?:-|\.\.))(\w{1,3})
:
>>> pattern= r"(?i)\b((?=\w{1,3}(?:-|\.\.))(\w{1,3})(-|\.{2,10})[\t ]?)+(\2\w{2,})"
>>> regex.sub(pattern, substitute, content)
'"Erm....yes. T-Thank you for that."'
Ответ 2
изменить: ошибка bug теперь устранена в регулярном выражении 2017.04.23
только что протестирован в Python 3.6.1, и оригинальный шаблон работает так же в re
и regex
Оригинальный обходной путь - вы можете использовать ленивый оператор +?
(то есть другое регулярное выражение, которое будет вести себя иначе, чем исходный шаблон в крайних случаях, таких как T...Tha....Thank
):
pattern = r"(?i)\b((\w{1,3})(-|\.{2,10})[\t ]?)+?(\2\w{2,})"
Ошибка в 2017.04.05 произошла из-за возврата, что-то вроде этого:
Неудачное более длинное совпадение создает пустую группу \2
, и концептуально она должна инициировать возврат к более короткому совпадению, где вложенная группа будет не пустой, но regex
, кажется, "оптимизирует" и не вычисляет более короткое совпадение с нуля, но использует некоторые кэшированные значения, забывая отменить обновление вложенных групп совпадений.
Пример жадного сопоставления ((\w{1,3})(\.{2,10})){1,3}
сначала попытается сделать 3 повторения, а затем возвратится к меньшему:
import re
import regex
content = '"Erm....yes. T..T...Thank you for that."'
base_pattern_template = r'((\w{1,3})(\.{2,10})){%s}'
test_cases = ['1,3', '3', '2', '1']
for tc in test_cases:
pattern = base_pattern_template % tc
expected = re.findall(pattern, content)
actual = regex.findall(pattern, content)
# TODO: convert to test case, e.g. in pytest
# assert str(expected) == str(actual), '{}\nexpected: {}\nactual: {}'.format(tc, expected, actual)
print('expected:', tc, expected)
print('actual: ', tc, actual)
выход:
expected: 1,3 [('Erm....', 'Erm', '....'), ('T...', 'T', '...')]
actual: 1,3 [('Erm....', '', '....'), ('T...', '', '...')]
expected: 3 []
actual: 3 []
expected: 2 [('T...', 'T', '...')]
actual: 2 [('T...', 'T', '...')]
expected: 1 [('Erm....', 'Erm', '....'), ('T..', 'T', '..'), ('T...', 'T', '...')]
actual: 1 [('Erm....', 'Erm', '....'), ('T..', 'T', '..'), ('T...', 'T', '...')]