Python 为什么要花这么长时间才能匹配?是虫子吗?
我需要匹配web应用程序中的某些URL,即Python 为什么要花这么长时间才能匹配?是虫子吗?,python,regex,performance,state-machine,Python,Regex,Performance,State Machine,我需要匹配web应用程序中的某些URL,即/123456789,并编写此正则表达式以匹配模式: r'(\d+(,)?)+/$' re.findall(r'(\d+(,)?)+/$', '12345121,223456,123123,3234,4523,523523') 我注意到,即使在测试模式的几分钟后,它似乎也不会进行评估: r'(\d+(,)?)+/$' re.findall(r'(\d+(,)?)+/$', '12345121,223456,123123,3234,4523,5235
/123456789
,并编写此正则表达式以匹配模式:
r'(\d+(,)?)+/$'
re.findall(r'(\d+(,)?)+/$', '12345121,223456,123123,3234,4523,523523')
我注意到,即使在测试模式的几分钟后,它似乎也不会进行评估:
r'(\d+(,)?)+/$'
re.findall(r'(\d+(,)?)+/$', '12345121,223456,123123,3234,4523,523523')
预期结果是没有匹配项
但是,此表达式几乎立即执行(请注意后面的斜杠):
这是一个bug吗?根据不匹配字符串的长度,会发生一些导致处理量呈指数级增长的情况。这与嵌套重复和可选逗号有关(即使某些正则表达式引擎可以确定这与尝试所有无关重复不匹配)。这可以通过优化表达式来解决
实现这一点的最简单方法是查找1+位或逗号,后跟斜杠和字符串结尾:。然而,这并不完美,因为它将允许类似于
、123、、4,5/
的内容
为此,您可以使用初始尝试的稍微优化版本:。首先,我创建了您的重复组((?:…)
),这不是必需的,但它提供了“更清晰的匹配”下一步,也是唯一关键的一步,我停止在组内重复\d
,因为组已在重复。最后,我删除了可选周围不必要的组,
,因为?
只影响最后一个字符。这将查找一个数字,可能是逗号,然后重复,最后是尾随的/
这仍然可以匹配一个奇数字符串
1,2,3,/
,因此我改进了原来的正则表达式,增加了一个:。这将断言尾随的/
前面没有逗号首先,我必须说它不是一个BUG。正因为如此,它尝试了所有的可能性,它需要时间和计算资源。有时它会占用很多时间。当它变得非常糟糕时,它被称为灾难性回溯
这是代码中findall
函数的代码:
正如您所看到的,它只是使用函数,因此基于实际使用python用于正则表达式匹配的传统NFA
函数的\u compile()
,并基于
Jeffrey E.F.Friedl在《掌握正则表达式》第三版中简要介绍了正则表达式中的回溯
NFA
引擎的本质是:它依次考虑每个子表达式或组件,每当需要在两个同样可行的选项之间做出决定时,
它选择一个,并记住另一个,以便在需要时稍后返回。
它必须在行动方案中做出决定的情况包括任何具有
量词(决定是否尝试另一个匹配)和替换(决定哪一个匹配)
将native改为try,并将其留待以后使用)。
无论尝试哪种操作过程,如果成功,以及正则表达式的其余部分
比赛也成功了,比赛结束了。如果正则表达式其余部分中的任何内容最终导致失败,正则表达式引擎知道它可以返回到它选择的位置
第一个选项,可以通过尝试其他选项继续匹配。这边
它最终尝试了正则表达式的所有可能的排列(或至少尽可能多的排列)
在找到匹配项之前需要)
让我们进入您的模式:因此您有r'(\d+(,)?)+/$”
这个字符串“123451212234561231233234523523523”
我们有以下步骤:
- 首先,字符串的第一部分(
)与12345121
匹配,然后\d+
与,
匹配(,)?
- 然后根据第一步,由于分组(
)之后的(\d+(,)?)+
,整个字符串是匹配的++
- 然后在最后,没有要匹配的
。因此,/$
需要“回溯”到上次检查(\d+(,)?)++
之前的一个字符。同样,它找不到任何合适的匹配项,因此在此之后(/$
)轮到回溯,然后,
将回溯,此回溯将继续结束,直到返回\d+
无。 因此,根据字符串的长度,它需要时间,在本例中,时间非常长,并且它会创建一个完全嵌套的量词李>
'12345121,223456,123123,3234,4523,' 3^33 = 5.559060567×10¹⁵
~/Desktop $ time python ex.py
real 0m3.814s
user 0m3.818s
sys 0m0.000s
'12345121,223456,123123,3234,4523,5' 3^24 = 1.66771817×10¹⁶ #X2 before
~/Desktop $ time python ex.py
real 0m5.846s
user 0m5.837s
sys 0m0.015s
'12345121,223456,123123,3234,4523,523' 3^36= 1.500946353×10¹⁷ #~10X before
~/Desktop $ time python ex.py
real 0m15.796s
user 0m15.803s
sys 0m0.008s
因此,为了避免此问题,您可以使用以下方法之一:
- (目前在Python中不支持,创建了一个用于将其添加到Python 3中的)
- 通过将嵌套组拆分为单独的正则表达式来减少回溯的可能性
- 我建议避免灾难性的回溯
r'\d+(,\d+)*/$'
这是一篇很好的文章,可以帮助您理解这种情况。相关:和。值得注意的是,有一些regex实现没有这些病态案例——例如,请参阅最终将取代
re
,对于性能,我建议:\d(?:\d |,\d)*+/
快速简单,只匹配正确的字符串来回答OP问题的第二部分:是的,这是一个bug(在Python中)。@R。。我认为把一个表现糟糕的算法称为“bug”是不公平的。如果有人提出了n^2排序,我不会告诉他们他们的代码中有错误,只是这不是好的或有效的代码。我不知道为什么这篇文章获得了这么多选票,而它没有