如果更改python中的compile和findall的顺序,为什么性能会不同

时间:2018-11-12 08:00:26

标签: python regex compilation findall

我注意到通过编译模式进行预处理将加快匹配操作,就像下面的示例一样。

python3 -m timeit -s "import re; t = re.compile(r'[\w+][\d]+')" "t.findall('abc eft123&aaa123')"

1000000 loops, best of 3: 1.42 usec per loop

python3 -m timeit -s "import re;" "re.findall(r'[\w+][\d]+', 'abc eft123&aaa123')"

100000 loops, best of 3: 2.45 usec per loop

但是,如果我更改编译模式的顺序并重新进行模块化,结果会有所不同,现在看来速度要慢得多,为什么会这样?

python3 -m timeit -s "import re; t = re.compile(r'[\w+][\d]+')" "re.findall(t, 'abc eft123&aaa123')"

100000 loops, best of 3: 3.66 usec per loop

3 个答案:

答案 0 :(得分:1)

通过“更改顺序”,您实际上以“静态”形式使用findall,几乎等同于调用str.lower('ABC')而不是'ABC'.lower()

取决于您使用的Python解释器的确切实现,这可能会导致一些开销(例如,用于方法查找)。

换句话说,这与Python的工作方式更相关,而与regex或re模块无关。

from timeit import Timer

def a():
    str.lower('ABC')

def b():
    'ABC'.lower()

print(min(Timer(a).repeat(5000, 5000)))
print(min(Timer(b).repeat(5000, 5000)))

输出

0.001060427000000086    # str.lower('ABC')
0.0008686820000001205   # 'ABC'.lower()

答案 1 :(得分:0)

让我们说word1,word2 ...是正则表达式:

让我们重写这些部分:

allWords = [re.compile(m) for m in ["word1", "word2", "word3"]]

我将为所有模式创建一个正则表达式:

allWords = re.compile("|".join(["word1", "word2", "word3"])

使用|支持正则表达式在其中,您必须在表达式中加上括号:

allWords = re.compile("|".join("({})".format(x) for x in ["word1", "word2", "word3"])

(当然,它也适用于标准单词,由于|部分,仍然值得使用正则表达式)

现在这是每个术语都经过硬编码的伪装循环:

def bar(data, allWords):
   if allWords[0].search(data) != None:
      temp = data.split("word1", 1)[1]  # that works only on non-regexes BTW
      return(temp)

   elif allWords[1].search(data) != None:
      temp = data.split("word2", 1)[1]
      return(temp)

可以简单地重写为

def bar(data, allWords):
   return allWords.split(data,maxsplit=1)[1]

在性能方面:

正则表达式是在开始时编译的,因此它要尽可能快 没有循环或粘贴的表达式,“或”部分由正则表达式引擎完成,这在大多数情况下是一些编译后的代码:在纯python中无法胜任。 匹配和拆分在一键操作中完成 最后一个麻烦是内部正则表达式引擎在循环中搜索所有表达式,这使该算法成为O(n)算法。为了使其更快,您必须预测哪种模式最频繁,然后将其放在第一位(我的假设是正则表达式是“不相交的”,这意味着文本不能被多个匹配,否则最长的文本必须被匹配)。在矮个子之前出现)

答案 2 :(得分:0)

我花了一些时间研究re.findallre.match的实现,并在此处复制了标准库源代码。

def findall(pattern, string, flags=0):
    """Return a list of all non-overlapping matches in the string.

    If one or more capturing groups are present in the pattern, return
    a list of groups; this will be a list of tuples if the pattern
    has more than one group.

    Empty matches are included in the result."""
    return _compile(pattern, flags).findall(string)


def match(pattern, string, flags=0):
    """Try to apply the pattern at the start of the string, returning
    a match object, or None if no match was found."""
    return _compile(pattern, flags).match(string)


def _compile(pattern, flags):
    # internal: compile pattern
    try:
        p, loc = _cache[type(pattern), pattern, flags]
        if loc is None or loc == _locale.setlocale(_locale.LC_CTYPE):
            return p
    except KeyError:
        pass
    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")
    p = sre_compile.compile(pattern, flags)
    if not (flags & DEBUG):
        if len(_cache) >= _MAXCACHE:
            _cache.clear()
        if p.flags & LOCALE:
            if not _locale:
                return p
            loc = _locale.setlocale(_locale.LC_CTYPE)
        else:
            loc = None
        _cache[type(pattern), pattern, flags] = p, loc
    return p

这表明如果我们直接执行re.findall(compiled_pa​​ttern,string),它将触发_compile(pattern,flags)的附加调用,在该函数中它将执行一些检查并在缓存字典中搜索模式。但是,如果我们改为调用compile_pattern.findall(string),则该“附加操作”将不存在。因此compile_pattern.findall(string)比re.findall(compile_pattern,string)