Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/actionscript-3/7.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 为什么要花这么长时间才能匹配?是虫子吗?_Python_Regex_Performance_State Machine - Fatal编程技术网

Python 为什么要花这么长时间才能匹配?是虫子吗?

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

我需要匹配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,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+
    将回溯,此回溯将继续结束,直到返回
    无。
    因此,根据字符串的长度,它需要时间,在本例中,时间非常长,并且它会创建一个完全嵌套的量词
    
作为近似基准,在本例中,您具有39字符,因此您需要3^39次回溯尝试(我们有3回溯方法)

现在为了更好地理解,我在更改字符串长度的同时测量程序的运行时:

'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排序,我不会告诉他们他们的代码中有错误,只是这不是好的或有效的代码。我不知道为什么这篇文章获得了这么多选票,而它没有