当字符串中有空格时,Python正则表达式会变慢

时间:2013-08-28 10:25:03

标签: python regex

我想使用Pythons regex模块匹配字符串。

在我的情况下,我想验证字符串是否以“_”组合开头,结尾和由大写字母组成。例如,以下字符串有效:“MY_HERO2”。以下字符串无效:“_MY_HREO2”,“我的HERO2”,“MY_HERO2 _”

要验证字符串,请使用以下代码:

import re
my_string = "MY_HERO"   

p = re.compile("^([A-Z,0-9]+_??)+[A-Z,0-9]$")
if p.match(my_string):
    print "validated"

那么我的问题是什么?验证包含空格的长字符串非常非常慢。我怎么能避免这个?我的模式错了吗?这种行为的原因是什么?

以下是一些数字:

MY_HERO2 --> 53 ms
MY_SUPER_GREAT_UNBELIEVABLE_HERO --> 69 microseconds
MY_SUPER_GREAT_UNBELIEVABLE HERO --> 223576 microseconds
MY_SUPER_GREAT_UNBELIEVABLE_STRONG_HERO --> 15 microseconds
MY_SUPER_GREAT_UNBELIEVABLE_STRONG HERO --> 979429 microseconds

感谢您的提名和回复。 :-) 保罗

2 个答案:

答案 0 :(得分:13)

  

那我的问题是什么?

问题是catastrophic backtracking。正则表达式引擎尝试了很多变化,这需要花费很多时间。

让我们用一个非常简单的例子来试试:A_B D

引擎首先将A[A-Z,0-9]+匹配,然后它会尝试_??,但由于它是可选的(懒惰),它现在会跳过它,这个已完成([A-Z,0-9]+_??)+

现在引擎尝试匹配[A-Z,0-9],但字符串中有一个_因此它失败了,现在它需要回溯,所以它重新进入([A-Z,0-9]+_??)+失败了上次尝试_??并成功。

现在引擎再次退出([A-Z,0-9]+_??)+并尝试匹配[A-Z,0-9]并成功,然后尝试匹配字符串结尾$但失败,现在它回溯并进入{ {1}}再次。

我希望你能看到它的发展方向,因为我已经分层编写它并且我们还没有到达空格字符 - 实际上你的正则表达式中没有接受的任何字符,例如([A-Z,0-9]+_??)+或{{ 1}}等会产生这个,而不仅仅是空白 - 这是一个很小的例子,在你的长字符串的情况下,它将不得不这样做数百次,直到它能够匹配整个字符串或失败,因此大量的时间。

  

验证包含空格的长字符串非常非常慢。

这也是由于回溯和地狱般的变化。

  

我该如何避免这种情况?

您可以改用此正则表达式:

#

这可确保字符串以大写或数字开头,后跟可选的%,重复此操作一次或多次,并确保最后有一个大写或数字。

  

我的模式错了吗?这种行为的原因是什么?

你的表达没错,但效率很低。

^([A-Z0-9]_?)*[A-Z0-9]$

答案 1 :(得分:0)

使用这个简单的正则表达式可以获得相同的结果:

import timeit

stmt = '''
import re
reg = '^(?:[A-Z0-9][A-Z0-9_]*)?[A-Z0-9]$'
p = re.compile(reg)
p.match('%s')
'''

str_list = ['MY_HERO2', 'MY_SUPER_GREAT_UNBELIEVABLE_HERO', 'MY_SUPER_GREAT_UNBELIEVABLE HERO', 'MY_SUPER_GREAT_UNBELIEVABLE_STRONG_HERO', 'MY_SUPER_GREAT_UNBELIEVABLE_STRONG HERO']

for s in str_list:
    t = timeit.timeit(stmt%s, number=1000)
    print '%s: %s' % (s, t)

import re
reg = '^(?:[A-Z0-9][A-Z0-9_]*)?[A-Z0-9]$'
p = re.compile(reg)
for s in str_list:
    result = p.match(s) is not None
    print '%s: %s' % (s, result)

结果:

MY_HERO2: 0.00375986099243
MY_SUPER_GREAT_UNBELIEVABLE_HERO: 0.00417900085449
MY_SUPER_GREAT_UNBELIEVABLE HERO: 0.00534510612488
MY_SUPER_GREAT_UNBELIEVABLE_STRONG_HERO: 0.00419306755066
MY_SUPER_GREAT_UNBELIEVABLE_STRONG HERO: 0.00567102432251

MY_HERO2: True
MY_SUPER_GREAT_UNBELIEVABLE_HERO: True
MY_SUPER_GREAT_UNBELIEVABLE HERO: False
MY_SUPER_GREAT_UNBELIEVABLE_STRONG_HERO: True
MY_SUPER_GREAT_UNBELIEVABLE_STRONG HERO: False