在Python中对正则表达式使用compile有什么好处吗?

h = re.compile('hello')
h.match('hello world')

vs

re.match('hello', 'hello world')

当前回答

用下面的例子:

h = re.compile('hello')
h.match('hello world')

上面例子中的匹配方法和下面的不一样:

re.match('hello', 'hello world')

Re.compile()返回一个正则表达式对象,这意味着h是一个正则表达式对象。

regex对象有自己的匹配方法,带有可选的pos和endpos参数:

的。匹配(字符串[线程][线程]])

pos

可选的第二个参数pos给出了字符串中的一个索引 搜寻就要开始了;缺省值为0。这并不完全是 相当于对字符串进行切片;'^'模式字符匹配于 字符串的真正开始和在a之后的位置 换行符,但不一定在搜索到的索引处 开始。

尾部

可选参数endpos限制了字符串的长度 搜索;这就好像字符串有endpos个字符那么长 只搜索从pos到endpos - 1的字符 匹配。如果endpos小于pos,则找不到匹配;否则, 如果rx是编译后的正则表达式对象,则rx。搜索(字符串,0, 50)等于rx。搜索(字符串(:50),0)。

regex对象的search、findall和finditer方法也支持这些参数。

Re.match (pattern, string, flags=0)不支持,如你所见, 它的search、findall和finditer也没有。

match对象具有补充这些参数的属性:

match.pos

的search()或match()方法传递的pos的值 一个正则表达式对象。这是正则表达式所在字符串的索引 引擎开始寻找匹配。

match.endpos

传递给search()或match()方法的endpos值 正则表达式对象的。对象超出的字符串的索引 RE引擎不会去。


一个regex对象有两个唯一的,可能有用的属性:

regex.groups

模式中捕获组的数量。

regex.groupindex

将(?P)定义的任何符号组名映射到的字典 组数字。如果没有使用符号组,则字典为空 在模式中。


最后,match对象有这个属性:

match.re

其match()或search()方法的正则表达式对象 生成此匹配实例。

其他回答

Ubuntu 22.04:

$ python --version
Python 3.10.6

$ for x in 1 10 100 1000 10000 100000 1000000; do python -m timeit -n $x -s 'import re' 're.match("[0-9]{3}-[0-9]{3}-[0-9]{4}", "123-123-1234")'; done
1 loop, best of 5: 972 nsec per loop
:0: UserWarning: The test results are likely unreliable. The worst time (186 usec) was more than four times slower than the best time (972 nsec).
10 loops, best of 5: 819 nsec per loop
:0: UserWarning: The test results are likely unreliable. The worst time (13.9 usec) was more than four times slower than the best time (819 nsec).
100 loops, best of 5: 763 nsec per loop
1000 loops, best of 5: 699 nsec per loop
10000 loops, best of 5: 653 nsec per loop
100000 loops, best of 5: 655 nsec per loop
1000000 loops, best of 5: 656 nsec per loop

$ for x in 1 10 100 1000 10000 100000 1000000; do python -m timeit -n $x -s 'import re' 'r = re.compile("[0-9]{3}-[0-9]{3}-[0-9]{4}")' 'r.match("123-123-1234")'; done
1 loop, best of 5: 985 nsec per loop
:0: UserWarning: The test results are likely unreliable. The worst time (134 usec) was more than four times slower than the best time (985 nsec).
10 loops, best of 5: 775 nsec per loop
:0: UserWarning: The test results are likely unreliable. The worst time (13.9 usec) was more than four times slower than the best time (775 nsec).
100 loops, best of 5: 756 nsec per loop
1000 loops, best of 5: 701 nsec per loop
10000 loops, best of 5: 704 nsec per loop
100000 loops, best of 5: 654 nsec per loop
1000000 loops, best of 5: 651 nsec per loop

FWIW:

$ python -m timeit -s "import re" "re.match('hello', 'hello world')"
100000 loops, best of 3: 3.82 usec per loop

$ python -m timeit -s "import re; h=re.compile('hello')" "h.match('hello world')"
1000000 loops, best of 3: 1.26 usec per loop

因此,如果您将经常使用同一个正则表达式,可能值得执行re.compile(特别是对于更复杂的正则表达式)。

反对过早优化的标准论点适用,但如果您怀疑regexp可能成为性能瓶颈,我不认为使用re.compile会真正失去多少清晰度/直接性。

更新:

在Python 3.6(我怀疑上述计时是使用Python 2.x完成的)和2018硬件(MacBook Pro)下,我现在得到以下计时:

% python -m timeit -s "import re" "re.match('hello', 'hello world')"
1000000 loops, best of 3: 0.661 usec per loop

% python -m timeit -s "import re; h=re.compile('hello')" "h.match('hello world')"
1000000 loops, best of 3: 0.285 usec per loop

% python -m timeit -s "import re" "h=re.compile('hello'); h.match('hello world')"
1000000 loops, best of 3: 0.65 usec per loop

% python --version
Python 3.6.5 :: Anaconda, Inc.

我还添加了一个案例(注意最后两次运行之间的引号差异),表明re.match(x,…)从字面上[大致]等价于re.compile(x).match(…),即似乎没有发生编译表示的幕后缓存。

这个答案可能姗姗来迟,但却是一个有趣的发现。如果你打算多次使用regex,使用compile真的可以节省你的时间(这在文档中也有提到)。下面你可以看到,当直接调用match方法时,使用编译后的正则表达式是最快的。将一个编译好的正则表达式传递给re.match会使它更慢,而将re.match与patter字符串传递在中间的某个地方。

>>> ipr = r'\D+((([0-2][0-5]?[0-5]?)\.){3}([0-2][0-5]?[0-5]?))\D+'
>>> average(*timeit.repeat("re.match(ipr, 'abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
1.5077415757028423
>>> ipr = re.compile(ipr)
>>> average(*timeit.repeat("re.match(ipr, 'abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
1.8324008992184038
>>> average(*timeit.repeat("ipr.match('abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
0.9187896518778871

有趣的是,编译对我来说确实更有效(Win XP上的Python 2.5.2):

import re
import time

rgx = re.compile('(\w+)\s+[0-9_]?\s+\w*')
str = "average    2 never"
a = 0

t = time.time()

for i in xrange(1000000):
    if re.match('(\w+)\s+[0-9_]?\s+\w*', str):
    #~ if rgx.match(str):
        a += 1

print time.time() - t

按原样运行上述代码一次,并以相反的方式运行两个if行,编译后的正则表达式的速度将提高一倍

我自己刚试过。对于从字符串中解析数字并对其求和的简单情况,使用编译后的正则表达式对象的速度大约是使用re方法的两倍。

正如其他人指出的那样,re方法(包括re.compile)在以前编译的表达式缓存中查找正则表达式字符串。因此,在正常情况下,使用re方法的额外成本只是缓存查找的成本。

然而,检查代码,缓存被限制为100个表达式。这就引出了一个问题,缓存溢出有多痛苦?该代码包含正则表达式编译器的内部接口re.sre_compile.compile。如果我们调用它,就绕过了缓存。结果表明,对于一个基本的正则表达式,例如r'\w+\s+([0-9_]+)\s+\w*',它要慢两个数量级。

下面是我的测试:

#!/usr/bin/env python
import re
import time

def timed(func):
    def wrapper(*args):
        t = time.time()
        result = func(*args)
        t = time.time() - t
        print '%s took %.3f seconds.' % (func.func_name, t)
        return result
    return wrapper

regularExpression = r'\w+\s+([0-9_]+)\s+\w*'
testString = "average    2 never"

@timed
def noncompiled():
    a = 0
    for x in xrange(1000000):
        m = re.match(regularExpression, testString)
        a += int(m.group(1))
    return a

@timed
def compiled():
    a = 0
    rgx = re.compile(regularExpression)
    for x in xrange(1000000):
        m = rgx.match(testString)
        a += int(m.group(1))
    return a

@timed
def reallyCompiled():
    a = 0
    rgx = re.sre_compile.compile(regularExpression)
    for x in xrange(1000000):
        m = rgx.match(testString)
        a += int(m.group(1))
    return a


@timed
def compiledInLoop():
    a = 0
    for x in xrange(1000000):
        rgx = re.compile(regularExpression)
        m = rgx.match(testString)
        a += int(m.group(1))
    return a

@timed
def reallyCompiledInLoop():
    a = 0
    for x in xrange(10000):
        rgx = re.sre_compile.compile(regularExpression)
        m = rgx.match(testString)
        a += int(m.group(1))
    return a

r1 = noncompiled()
r2 = compiled()
r3 = reallyCompiled()
r4 = compiledInLoop()
r5 = reallyCompiledInLoop()
print "r1 = ", r1
print "r2 = ", r2
print "r3 = ", r3
print "r4 = ", r4
print "r5 = ", r5
</pre>
And here is the output on my machine:
<pre>
$ regexTest.py 
noncompiled took 4.555 seconds.
compiled took 2.323 seconds.
reallyCompiled took 2.325 seconds.
compiledInLoop took 4.620 seconds.
reallyCompiledInLoop took 4.074 seconds.
r1 =  2000000
r2 =  2000000
r3 =  2000000
r4 =  2000000
r5 =  20000

'reallyCompiled'方法使用内部接口,绕过缓存。注意,在每个循环迭代中编译的代码只迭代了10,000次,而不是一百万次。