【问题标题】:python: how to interrupt a regex matchpython:如何中断正则表达式匹配
【发布时间】:2011-03-14 11:37:59
【问题描述】:

我遍历大量下载的文本文件中的行,并在每一行上进行正则表达式匹配。通常,比赛需要不到一秒钟的时间。然而,有时一场比赛需要几分钟,有时比赛根本没有完成,代码只是挂起(几次等了一个小时,然后放弃了)。因此,我需要引入某种超时,并以某种方式告诉正则表达式匹配代码在 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 人知道正则表达式 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, -

如前所述,正则表达式 通常 执行良好 - 我可以在不到一分钟的时间内解析数百行的数百个文件。但是,那是文件完整的时候-代码似乎与行不完整的文件一起挂起,例如

`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

我只是快速阅读了catastrophic article,但据我所知,这不是原因 - 我没有嵌套任何重复运算符。

我在 Mac OSX 上,所以我不能使用 RegexBuddy 来分析我的正则表达式。我尝试了RegExhibit(它显然在内部使用了 Perl RegEx 引擎)——它也跑掉了。

【问题讨论】:

  • Python 中的正则表达式匹配发生在内部 GIL,因此线程解决方案对您没有帮助:当正则表达式匹配运行时,没有其他线程可以运行。
  • 这种性能问题通常是由于灾难性的回溯造成的。您很可能可以改进您的正则表达式以更快地匹配(或失败)。请发布您正在使用的正则表达式(以及您与之匹配的一些示例数据),以便我们查看它。解决问题,而不是症状:)

标签: python regex multithreading timeout signals


【解决方案1】:

你正在遭遇灾难性的回溯;不是因为嵌套量词,而是因为你的量化字符也可以匹配分隔符,而且由于它们很多,在某些情况下你会得到指数时间。

除了看起来更像 CSV 解析器的工作之外,请尝试以下操作:

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

通过明确禁止逗号在分隔符之间进行匹配,您将大大加快正则表达式的速度。

例如,如果逗号可能出现在带引号的字符串中,则只需将[^,]+(在您期望的地方)与

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

举例说明:

对第一个示例使用您的正则表达式,RegexBuddy 在正则表达式引擎的 793 步后报告成功匹配。对于第二个(不完整的行)示例,它在正则表达式引擎的 1.000.000 步后报告匹配失败(这是 RegexBuddy 放弃的地方;Python 将继续搅动)。

使用我的正则表达式,成功匹配发生在 173 步内,失败发生在 174 步内。

【讨论】:

  • 魔法。感觉我在问题面前站得太近了。谢了哥们! :-)
【解决方案2】:

你不能用线程来做到这一点。继续你的想法,在一个单独的过程中进行匹配。

【讨论】:

  • 我发现信号也不能正常工作:信号处理后匹配才继续......
  • 哦,不……信号在这种情况下确实有效:来自信号处理程序的异常将传播到匹配进程。见bugs.python.org/issue846388
  • 我现在正经过这个区域。 .NET 在其正则表达式中引入了超时。我认为 Python 也可以这样做吗?免费的正则表达式搜索并不少见,包括。在文本编辑器中。此外,在实践中创建有问题的表达式可能比人们想象的更容易计算复杂......docs.microsoft.com/en-us/dotnet/api/…
【解决方案3】:

与其尝试通过超时解决正则表达式挂起问题,不如考虑一种完全不同的方法。如果您的数据真的只是逗号分隔的值,您应该使用csv-module 或仅使用line.split(",") 获得更好的性能。

【讨论】:

  • 是的,看起来确实如此。虽然改变一般方法需要付出很大的努力,但有很多原因,请参阅stackoverflow.com/questions/5160405/… 我在项目的早期就研究了 CSV 模块,它似乎并没有给我任何有用的东西Python 不能开箱即用。看起来我可能不得不考虑一种特定于文件类型的多阶段方法(我希望我可以避免这种方法......)
  • 呵呵,python自带csv模块
【解决方案4】:

Python 中的线程是一头奇怪的野兽。全局解释器锁本质上是解释器周围的一个大锁,这意味着一次只有一个线程可以在解释器中执行。

线程调度被委托给操作系统。 Python本质上是向操作系统发出信号,表明另一个线程可能会在一定数量的“指令”之后获得锁定。因此,如果 Python 由于正则表达式失控而忙碌,它永远不会有机会向操作系统发出信号,表明它可能会尝试为另一个线程获取锁。因此使用信号的原因;他们是唯一的打断方式。

我在 Nosklo,继续使用单独的流程。或者,尝试重写正则表达式,使其不会逃跑。见the problems associated with backtracking。这可能是也可能不是正则表达式性能不佳的原因,并且可能无法更改您的正则表达式。但是,如果这是原因并且可以更改,那么您可以避免多个进程,从而为自己省去很多麻烦。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-11
    • 2016-07-01
    • 2013-09-22
    • 2019-09-12
    • 1970-01-01
    • 1970-01-01
    • 2015-09-14
    • 2021-12-31
    相关资源
    最近更新 更多