是否值得使用Python的re.compile?

2024-04-20 10:15:39 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

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

Tags: rehelloworldmatchcompile
3条回答

我有很多运行编译的regex 1000次的经验,也没有注意到任何可感知的差异。显然,这是一个轶事,当然也不是反对编译的有力论据,但我发现两者之间的差别可以忽略不计。

编辑: 在快速浏览了实际的Python2.5库代码之后,我发现无论何时使用正则表达式(包括对re.match()的调用),Python都会在内部编译和缓存正则表达式,因此只有当正则表达式被编译时,您才会真正地改变,而且根本不应该节省太多时间—只需要检查缓存所需的时间(对内部dict类型的键查找)。

来自模块re.py(注释是我的):

def match(pattern, string, flags=0):
    return _compile(pattern, flags).match(string)

def _compile(*key):

    # Does cache check at top of function
    cachekey = (type(key[0]),) + key
    p = _cache.get(cachekey)
    if p is not None: return p

    # ...
    # Does actual compilation on cache miss
    # ...

    # Caches compiled regex
    if len(_cache) >= _MAXCACHE:
        _cache.clear()
    _cache[cachekey] = p
    return p

我仍然经常预编译正则表达式,但只是将它们绑定到一个好的、可重用的名称,而不是为了获得任何预期的性能提升。

前进方向:

$ 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

因此,如果您要经常使用相同的regex,那么做re.compile(特别是对于更复杂的regex)可能是值得的。

反对过早优化的标准论据适用,但如果您怀疑regexp可能会成为性能瓶颈,那么使用re.compile并不会真正失去清晰性/直截了当性。

更新:

在Python3.6(我怀疑上述计时是使用Python2.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.

我还添加了一个case(注意最后两次运行之间的引号差异),它表明re.match(x, ...)实际上[大致]相当于re.compile(x).match(...),即编译表示的后台缓存似乎不会发生。

对我来说,re.compile的最大好处是能够将regex的定义与其使用分离开来。

即使是一个简单的表达式,如0|[1-9][0-9]*(基数10中不带前导零的整数),也可能非常复杂,以至于您不必重新键入它,检查是否有任何输入错误,稍后在开始调试时必须重新检查是否有输入错误。另外,使用变量名(如num或numúb10)比使用0|[1-9][0-9]*更好。

当然,存储字符串并将其传递给re.match是可能的;但是,这会使的可读性降低:

num = "..."
# then, much later:
m = re.match(num, input)

与编译相比:

num = re.compile("...")
# then, much later:
m = num.match(input)

虽然很接近,但第二行的最后一行在重复使用时感觉更自然、更简单。

相关问题 更多 >