我有一个慢速python正则表达式,如果我只是删除正则表达式的最后一行,速度会增加两个数量级!这是我的再现示例:
import re
import timeit
mystr = "14923KMLI MLI2010010206581258 0.109 b M M M 0 09 60+ "
basere = r"""
(?P<wban>[0-9]{5})
(?P<faaid>[0-9A-Z]{4})\s
(?P<id3>[0-9A-Z]{3})
(?P<tstamp>[0-9]{16})\s+
\[?\s*((?P<vis1_coef>\-?\d+\.\d*)|(?P<vis1_coef_miss>M))\s*\]?\s*
\[?(?P<vis1_nd>[0-9A-Za-z\?\$/ ])\]?\s+
((?P<vis2_coef>\d+\.\d*)|(?P<vis2_coef_miss>[M ]))\s+(?P<vis2_nd>[A-Za-z\?\$ ])\s+
...............\s+
\[?\s*((?P<drct>\d+)|(?P<drct_miss>M))\s+
((?P<sknt>\d+)|(?P<sknt_miss>M))\s+
((?P<gust_drct>\d+)\+?|(?P<gust_drct_miss>M))\s*\]?\s+
"""
additional = r"""
\[?((?P<gust_sknt>\d+)R?L?F*\d*\+?|(?P<gust_sknt_miss>M))\s*\]?\s+
"""
P1_RE = re.compile(basere + additional, re.VERBOSE)
P2_RE = re.compile(basere, re.VERBOSE)
for myre in ["P1_RE", "P2_RE"]:
statement = "%s.match('%s')" % (myre, mystr)
res = timeit.timeit(statement, "from __main__ import %s" % (myre,),
number=1000)
print('%s took %.9f per iteration' % (myre, res / 1000.))
# result on my laptop, python 2.6 and 3.3 tested
# P1_RE took 0.001489143 per iteration
# P2_RE took 0.000019991 per iteration
因此P1_RE
和P2_RE
之间的唯一区别是额外的正则表达式。关于我做错了什么想法?
答案 0 :(得分:4)
这是因为回溯;这是正则表达式效率低下的一个重要原因。
您删除的最后一行要求至少匹配1个数字或M
,后跟空格,以及许多可选项。
倒数第二行:
((?P<gust_drct>\d+)\+?|(?P<gust_drct_miss>M))\s*\]?\s+
最初匹配最后一部分,即60+
和后面的空格,不留最后一行。因此,正则表达式无法匹配并一次回溯一个字符以尝试匹配最后一行。事实上,正则表达式会尝试匹配很多东西,对于正则表达式回溯的每个字符,有越来越多的匹配尝试。
在正则表达式匹配之前,它实际上拒绝了之前正则表达式之前匹配的很多字符。
一个简单的例子是字符串(10 a):
aaaaaaaaaa
正则表达式:
a+a+a+a+a+a+a+a+a+a+
正则表达式a+
的第一部分将匹配整个字符串(因为+
是贪婪的),但是,它需要匹配更多a
,因为正则表达式的下一部分是a+
。引擎然后回溯一个字符,以便第一个a+
匹配aaaaaaaaa
(9个),第二个匹配最后一个a
。
接下来还有第三个a+
,因为不再需要a
匹配,正则表达式将回溯一个字符。由于第二个a
无法回溯,因此第一个a+
会匹配aaaaaaaa
(8个),然后第二个a+
将与最后一个{{1}匹配}}。现在不再有aa
,因此第二个a
将回溯以匹配倒数第二个a+
,最后,第三个a
可以匹配最后一个a+
}。
看看前3 a
做了多少?现在想象一下,通过每一个可能的匹配,直到所有匹配?
通常,如果你根本不想回溯,你会使用原子组和占有量词,但是python不支持那些,至少不支持a+
模块(regex
然而,模块确实如此。
你可能想要修改你的正则表达式并试着看看它应该真正匹配什么,什么是真正可选的,特别是你的捕获组中可能的空格。