Python的正则表达式模式缓存如何工作?

ver*_*ald 3 python regex performance python-2.7

来自Python文档re.compile():

注意传递给re.match(),re.search()或re.compile()的最新模式的编译版本被缓存,因此一次只使用几个正则表达式的程序不必担心定期编译表达式.

但是,在我的测试中,这个断言似乎没有成功.在对重复使用相同模式的以下片段进行计时时,编译版本仍然比未编译版本(应该被缓存)快得多.

我在这里找不到能解释时差的东西吗?

import timeit

setup = """
import re
pattern = "p.a.t.t.e.r.n"
target = "p1a2t3t4e5r6n"
r = re.compile(pattern)
"""

print "compiled:", \
    min(timeit.Timer("r.search(target)", setup).repeat(3, 5000000))
print "uncompiled:", \
    min(timeit.Timer("re.search(pattern, target)", setup).repeat(3, 5000000))
Run Code Online (Sandbox Code Playgroud)

结果:

compiled: 2.26673030059
uncompiled: 6.15612802627
Run Code Online (Sandbox Code Playgroud)

mgi*_*son 8

这是(CPython)实现re.search:

def search(pattern, string, flags=0):
    """Scan through string looking for a match to the pattern, returning
    a match object, or None if no match was found."""
    return _compile(pattern, flags).search(string)
Run Code Online (Sandbox Code Playgroud)

这是re.compile:

def compile(pattern, flags=0):
    "Compile a regular expression pattern, returning a pattern object."
    return _compile(pattern, flags)
Run Code Online (Sandbox Code Playgroud)

它依赖于re._compile:

def _compile(*key):
    # internal: compile pattern
    cachekey = (type(key[0]),) + key
    p = _cache.get(cachekey)            #_cache is a dict.   
    if p is not None:
        return p
    pattern, flags = key
    if isinstance(pattern, _pattern_type):
        if flags:
            raise ValueError('Cannot process flags argument with a compiled pattern')
        return pattern 
    if not sre_compile.isstring(pattern):
        raise TypeError, "first argument must be string or compiled pattern"
    try:
        p = sre_compile.compile(pattern, flags)
    except error, v:
        raise error, v # invalid expression
    if len(_cache) >= _MAXCACHE:
        _cache.clear()
    _cache[cachekey] = p
    return p
Run Code Online (Sandbox Code Playgroud)

所以你可以看到,只要正则表达式已经在字典中,所涉及的唯一额外工作是字典中的查找(包括创建一些临时元组,一些额外的函数调用......).

更新 在好的日子里(上面复制的代码),当缓存太大时,缓存曾经完全失效.这些天,缓存周期 - 首先删除最旧的项目.这个实现依赖于python词典的排序(这是一个实现细节,直到python3.7).在python3.6之前的Cpython中,这会从缓存中删除一个任意值(这可能比使整个缓存无效更好)

  • re.search必须这样做,再加上一个额外的函数调用(re._compile)。但这几乎就是全部。也许您的正则表达式很简单(并通过_compile进行了充分优化),使得python中的那些小东西成为了计算的重要部分。还要注意,如果您有超过100个正则表达式(_MAXCACHE),则缓存将被清除,您需要重新开始,但是在这种情况下不应该对您造成影响。 (2认同)
  • 似乎只是小事加起来。尝试使用更复杂的模式/更长的输入字符串,`.search` 仍然较慢,但在 500 万次调用中,差异保持在大约 4 秒。所以我想结论是有一个小的开销,并且建议“一次只使用几个正则表达式的程序不必担心编译正则表达式”如果你在做 ** 可能并不完全准确很多**的搜索(但大多数时候不值得)。 (2认同)
  • 请注意,在最新版本的python中,此缓存已成为最近添加最少的512项缓存:https://github.com/python/cpython/blob/e42b705188271da108de42b55d9344642170aa2b/Lib/re.py#L270 (2认同)