我写了一个小的,天真的正则表达式,应该在括号内找到文本:
re.search(r'\((.|\s)*\)', name)
我知道这不是出于某些原因这样做的最好方法,但它工作得很好.我正在寻找的只是一个解释,为什么对于某些字符串,这个表达式开始呈指数级增长然后永远不会完成.昨晚,在运行此代码数月之后,我们的一台服务器突然卡住了匹配类似于以下内容的字符串:
x (y) z
Run Code Online (Sandbox Code Playgroud)
我已经对它进行了实验,并确定在"y"和"z"之间的每个空间所花费的时间都会翻倍:
In [62]: %timeit re.search(r'\((.|\s)*\)', 'x (y)' + (22 * ' ') + 'z')
1 loops, best of 3: 1.23 s per loop
In [63]: %timeit re.search(r'\((.|\s)*\)', 'x (y)' + (23 * ' ') + 'z')
1 loops, best of 3: 2.46 s per loop
In [64]: %timeit re.search(r'\((.|\s)*\)', 'x (y)' + (24 * ' ') + 'z')
1 loops, best of 3: 4.91 s per loop
Run Code Online (Sandbox Code Playgroud)
但是除了空格之外的字符也没有相同的效果:
In [65]: %timeit re.search(r'\((.|\s)*\)', 'x (y)' + (24 * 'a') + 'z')
100000 loops, best of 3: 5.23 us per loop
Run Code Online (Sandbox Code Playgroud)
注意:我不是在寻找更好的正则表达式或此问题的其他解决方案.我们不再使用它了.
正如CaffGeek的答案正确暗示的那样,问题是由于某种形式的灾难性回溯.这两个选项都匹配一个空格(或制表符),这是无限次地贪婪地应用.此外,点匹配右括号,因此一旦打开paren匹配,此表达式始终匹配字符串的最末端,然后必须艰苦地回溯以找到结束括号.在此回溯过程中,在每个位置尝试另一种替代方案(对于空格或制表符也是成功的).因此,必须在引擎可以回溯一个位置之前尝试每个可能的匹配组合序列.在收盘后有很多空间,这很快就会增加.有一个匹配的紧密paren的情况下的具体问题可以通过简单地使星形量化器懒惰(即r'\((.|\s)*?\)'
)来解决,但是对于不匹配的情况仍然存在失控的正则表达式问题,其中存在没有匹配关闭的开放paren主题字符串中的paren.
原来的正则表达式非常非常糟糕!(如果有多对,也不能正确匹配关闭的parens).
正确的表达式匹配最里面的括号(匹配和非匹配情况都非常快),当然是:
re_innermostparens = re.compile(r"""
\( # Literal open paren.
[^()]* # Zero or more non-parens.
\) # Literal close paren.
""", re.VERBOSE)
Run Code Online (Sandbox Code Playgroud)
在Jeffrey Friedl 必须阅读的正则表达式作者:掌握正则表达式(第3版)中,对此进行了详细解释(详尽的例子和推荐的最佳实践).我可以诚实地说,这是我读过的最有用的书.正则表达式是一个非常强大的工具,但像装载的武器必须非常小心和精确地应用(或者你会在脚下射击!)
可能是ReDos问题.
请参阅:http://en.wikipedia.org/wiki/ReDoS
可以在构造不良的正则表达式上创建正则表达式拒绝服务.
例如,从这里:https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS
这个正则表达式 ^(a+)+$
对于输入aaaaX,上图中有16条可能的路径.但是对于aaaaaaaaaaaaaaaaX,有65536条可能的路径,并且每增加一条路径的数量是两倍.这是一个极端的情况,其中天真的算法是有问题的,因为它必须传递许多路径,然后失败.
我怀疑你的正则表达式的大部分问题是这(.|\s)
有点令人困惑,因为\s
已经包含在内.
.但创造了一个选择点.
编辑:我认为这是我读过的问题的一个更好的解释.
http://msdn.microsoft.com/en-us/magazine/ff646973.aspx
归档时间: |
|
查看次数: |
469 次 |
最近记录: |