首先,我必须说这不是BUG。正因为如此,它尝试了所有的可能性,它需要时间和计算资源。有时它会吞噬很多时间。当它变得非常糟糕时,它被称为灾难性回溯。
这是python source代码中findall函数的代码:
def findall(pattern, string, flags=0):
"""Return a list of all non-overlapping matches in the string.
If one or more groups are present in the pattern, return a
list of groups; this will be a list of tuples if the pattern
has more than one group.
Empty matches are included in the result."""
return _compile(pattern, flags).findall(string)
如您所见,它只使用 compile() 函数,因此基于实际使用 Traditional NFA 的 _compile() 函数,python 用于其正则表达式匹配,并基于
Mastering Regular Expressions, Third Edition,Jeffrey E. F. Friedl 中关于正则表达式回溯的简短说明!
NFA 引擎的本质是:它依次考虑每个子表达式或组件,并且每当需要在两个同样可行的选项之间做出决定时,
如果需要,它会选择一个并记住另一个以便稍后返回。
它必须在行动方案中做出决定的情况包括任何具有
量词(决定是否尝试另一个匹配)和交替(决定哪个
替代本地人尝试,以及留待以后使用)。
无论尝试哪种操作,如果它成功并且正则表达式的其余部分
也成功了,比赛结束。如果正则表达式的其余部分最终导致失败,正则表达式引擎知道它可以回溯到它选择的位置
第一个选项,并且可以通过尝试其他选项继续比赛。这边走,
它最终会尝试所有可能的正则表达式排列(或至少与
在找到匹配之前需要)。
让我们进入你的模式:所以你有r'(\d+(,)?)+/$'这个字符串'12345121,223456,123123,3234,4523,523523'我们有这个步骤:
- 首先,字符串的第一部分(
12345121)与\d+匹配,然后,与(,)?匹配。
- 然后基于第一步,由于
+分组后整个字符串匹配((\d+(,)?)+)
- 那么最后,
/$ 没有任何匹配项。因此,(\d+(,)?)+ 需要“回溯”到最后一个字符之前的一个字符,以检查 /$。再次,它没有找到任何合适的匹配,所以之后轮到(,)回溯,然后\d+将回溯,并且这个回溯将继续结束直到它返回None。
所以根据你的字符串的长度,它需要时间,在这种情况下非常高,它会创建一个完全嵌套的量词!
作为一个近似基准,在这种情况下,您有 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
因此,为避免此问题,您可以使用以下方法之一: