Почему мое регулярное выражение Python проверяет, что более одной группы занимает так много времени?

98
5

Этот вопрос возникает в Django-адресе, но проблема кажется общей.


Я хочу сопоставить URL-адреса, построенные следующим образом:


1,2,3,4,5,6/10,11,12/

Я использую регулярное выражение:


^(?P<apples>([0123456789]+,?)+)/(?P<oranges>([0123456789]+,?)+)/$

Когда я пытаюсь сопоставить его с "допустимым" URL (т.е. совпадающим), я получаю мгновенное совпадение:


In [11]: print datetime.datetime.now(); re.compile(r"^(?P<apples>([0123456789]+,?)+)/(?P<oranges>([0123456789]+,?)+)/$").search("114,414,415,416,417,418,419,420,113,410,411,412,413/1/"); print datetime.datetime.now()
2011-10-18 14:27:42.087883
Out[11]: <_sre.SRE_Match object at 0x2ab0960>
2011-10-18 14:27:42.088145

Однако, когда я пытаюсь сопоставить "недопустимый" URL (несоответствие), все регулярное выражение принимает величину времени, чтобы ничего не возвращать:


In [12]: print datetime.datetime.now(); re.compile(r"^(?P<apples>([0123456789]+,?)+)/(?P<oranges>([0123456789]+,?)+)/").search("114,414,415,416,417,418,419,420,113,410,411,412,413/"); print datetime.datetime.now()
2011-10-18 14:29:21.011342
2011-10-18 14:30:00.573270

Я предполагаю, что в движке regexp есть что-то, что замедляется, когда нужно сопоставить несколько групп. Есть ли обходной путь для этого? Может быть, мое регулярное выражение должно быть исправлено?

спросил(а) 2021-01-25T16:22:10+03:00 4 месяца, 3 недели назад
1
Решение
108

Это известный недостаток многих движков регулярных выражений, включая Python и Perl. Что происходит, так это то, что двигатель отступает и получает экспоненциальный взрыв возможных совпадений. Улучшенные механизмы регулярных выражений не используют обратное отслеживание для такого простого регулярного выражения.


Вы можете исправить это, избавившись от дополнительной запятой. Это то, что позволяет движку смотреть на строку типа 123 и решить, следует ли ее анализировать как (123) или (12)(3) или (1)(23) или (1)(2)(3). Это много совпадений, чтобы попробовать только три цифры, чтобы вы могли увидеть, как он быстро взорвется на пару десятков цифр.


^(?P<apples>[0-9]+(,[0-9]+)*)/(?P<oranges>[0-9]+(,[0-9]+)*)/$

Это заставит механизм регулярных выражений всегда группировать 123,456 как (123),(456) и никогда не как (12)(3),(4)(56) или что-то еще. Поскольку он будет соответствовать только одному способу, механизм обратного слежения не столкнется с комбинаторным взрывом возможных анализов. Опять же, лучшие механизмы регулярных выражений не страдают от этого недостатка.

Обновление: Если бы я писал это, я бы сделал это следующим образом:


^(?P<apples>[0-9,]+)/(?P<oranges>[0-9,]+)$

Это будет соответствовать нескольким фиктивным URL-адресам (например, ,/,), но вы всегда можете вернуть 404 после того, как вы разобрали и разбили его.


try:
apples = [int(x) for x in apples.split(',')]
except ValueError:
# return 404 error

ответил(а) 2021-01-25T16:22:10+03:00 4 месяца, 3 недели назад
77

Вы можете использовать это регулярное выражение:


^(?P<apples>(?:\d+,)*\d+)/(?P<oranges>(?:\d+,)*\d+)/$

\ d соответствует цифре

ответил(а) 2021-01-25T16:22:10+03:00 4 месяца, 3 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема