Python: как прервать совпадение с регулярным выражением - PullRequest
7 голосов
/ 14 марта 2011

Я перебираю строки в большом количестве загруженных текстовых файлов и сопоставляю регулярные выражения в каждой строке.Обычно матч занимает не более секунды.Однако иногда сопоставление занимает несколько минут, иногда сопоставление не заканчивается вообще, а код просто зависает (пару раз подождал час, потом сдался).Поэтому мне нужно ввести какое-то время ожидания и сказать коду соответствия регулярному выражению каким-то образом остановиться через 10 секунд или около того.Я могу жить с тем фактом, что я потеряю данные, которые должно было возвращать регулярное выражение.

Я попробовал следующее (что, конечно, уже представляет собой 2 разных решения на основе потоков, показанных в одном примере кода):

def timeout_handler():
    print 'timeout_handler called'

if __name__ == '__main__':
    timer_thread = Timer(8.0, timeout_handler)
    parse_thread = Thread(target=parse_data_files, args=(my_args))
    timer_thread.start()
    parse_thread.start()
    parse_thread.join(12.0)
    print 'do we ever get here ?'

но я не получаю ни timeout_handler called, ни do we ever get here ? строки в выводе, код просто застрял в parse_data_files.

Еще хуже, я не могудаже остановите программу с помощью CTRL-C, вместо этого мне нужно найти номер процесса python и убить этот процесс.Некоторые исследования показали, что ребята из Python знают, что код regex C убегает: http://bugs.python.org/issue846388

Я добился некоторого успеха с помощью сигналов:

signal(SIGALRM, timeout_handler)
alarm(8)
data_sets = parse_data_files(config(), data_provider)
alarm(0)

это получаетсямне строка timeout_handler called в выводе - и я все еще могу остановить свой скрипт, используя CTRL-C.Если я сейчас изменю обработчик timeout_handler следующим образом:

class TimeoutException(Exception): 
    pass 

def timeout_handler(signum, frame):
    raise TimeoutException()

и заключу фактический вызов в re.match(...) в предложении try ... except TimeoutException, совпадение с регулярным выражением действительно прерывается.К сожалению, это работает только в моем простом однопоточном скрипте-песочнице, который я использую, чтобы попробовать что-то.В этом решении есть несколько недостатков:

  • сигнал срабатывает только один раз, если имеется более одной проблемной строки, я застрял на второй
  • таймераотсчет начинается сразу же, а не тогда, когда начинается фактический синтаксический анализ
  • из-за GIL, я должен выполнить настройку всех сигналов в основном потоке, а сигналы принимаются только в основном потоке;это противоречит тому факту, что несколько файлов должны анализироваться одновременно в разных потоках - также возникает только одно глобальное исключение тайм-аута, и я не вижу, как узнать, в каком потоке мне нужно реагировать на него
  • Я уже несколько раз читал, что потоки и сигналы не очень хорошо сочетаются

Я также подумал о том, чтобы сопоставить регулярное выражение в отдельном процессе, но прежде чем я углубился в это, я подумал:Лучше проверить здесь, сталкивался ли кто-нибудь с этой проблемой раньше и мог бы дать мне несколько советов о том, как ее решить.

Обновление

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

'^(\d{5}), .+?, (\d{8}), (\d{4}), .+?, .+?,' + 37 * ' (.*?),' + ' (.*?)$'

пример данных:

95756, "KURN ", 20110311, 2130, -34.00, 151.21, 260, 06.0, -9999.0, -9999.0, -9999.0, -9999.0, -9999.0, -9999, -9999, 07.0, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -

AsТем не менее, регулярное выражение обычно работает нормально - я могу проанализировать несколько сотен файлов с несколькими сотнями строк менее чем за минуту.Однако, когда файлы завершены, код кажется зависшим с файлами с неполными строками, такими как, например,

`95142, "YMGD ", 20110311, 1700, -12.06, 134.23, 310, 05.0, 25.8, 23.7, 1004.7, 20.6, 0.0, -9999, -9999, 07.0, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999, -9999

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

Обновление 2

Я только быстро прочитал катастрофическую статью , но, насколько я могу судить, этоне причина - я не вкладываю никаких операторов повторения.

Я на Mac OSX, поэтому я не могу использовать RegexBuddy для анализа моего регулярного выражения.Я попытался RegExhibit (который, очевидно, использует движок Perl RegEx для внутреннего использования) - и это тоже убегает.

Ответы [ 4 ]

8 голосов
/ 14 марта 2011

Вы столкнулись с катастрофическим отступлением;не из-за вложенных квантификаторов, а потому, что ваши количественные символы также могут совпадать с разделителями, и так как их много, в некоторых случаях вы получите экспоненциальное время.

Помимо того факта, что оно больше похоже назадание для синтаксического анализатора CSV, попробуйте следующее:

r'^(\d{5}), [^,]+, (\d{8}), (\d{4}), [^,]+, [^,]+,' + 37 * r' ([^,]+),' + r' ([^,]+)$'

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

Если запятые могут присутствоватьнапример, внутри строк в кавычках просто замените [^,]+ (в тех местах, где вы этого ожидаете) на

(?:"[^"]*"|[^,]+)

Для иллюстрации:

Используя ваше регулярное выражение для первого примера, RegexBuddy сообщает об успешном совпадении после 793 шагов движка регулярных выражений.Для второго примера (неполная строка) он сообщает об ошибке совпадения после 1.000.000 шагов движка регулярных выражений (это то, где RegexBuddy сдается; Python будет продолжать набивать).

Используя мое регулярное выражение, успешное совпадение происходит за 173 шага, а сбой - за 174.

2 голосов
/ 14 марта 2011

Вместо того, чтобы пытаться решить проблему зависания регулярного выражения с тайм-аутами, возможно, стоило бы рассмотреть совершенно другой подход. Если ваши данные на самом деле являются значениями, разделенными запятыми, вы должны получить гораздо лучшую производительность с помощью модуля csv или просто с помощью line.split(",").

1 голос
/ 14 марта 2011

Threading в Python - странный зверь.Глобальная блокировка интерпретатора - это, по сути, одна большая блокировка вокруг интерпретатора, что означает, что в интерпретаторе может выполняться только один поток за один раз.

Планирование потоков делегируется ОС.По сути, Python сообщает ОС, что другой поток может получить блокировку после определенного количества «инструкций».Поэтому, если Python занят из-за убегающего регулярного выражения, у него никогда не будет шанса сообщить ОС, что он может попытаться взять блокировку для другого потока.Отсюда и причина использования сигналов;они единственный способ прервать.

Я с Nosklo, продолжаю и использую отдельные процессы.Или попробуйте переписать регулярное выражение, чтобы оно не сбежало.См. проблемы, связанные с возвратом .Это может или не может быть причиной плохой производительности регулярных выражений, и изменение вашего регулярного выражения может быть невозможным.Но если это причина и ее можно изменить, вы избавите себя от головной боли, избегая нескольких процессов.

1 голос
/ 14 марта 2011

Вы не можете сделать это с потоками.Продолжайте свою идею провести матч в отдельном процессе.

...