Модуль регулярных выражений 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', '...')]