代码之家  ›  专栏  ›  技术社区  ›  colin-zhou GirishB

如果改变python中编译和findall的顺序,性能为什么会不同

  •  2
  • colin-zhou GirishB  · 技术社区  · 7 年前

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

    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 回复  |  直到 7 年前
        1
  •  1
  •   DeepSpace    7 年前

    通过“改变顺序”你实际上在使用 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()
    
        2
  •  0
  •   Adrian W    7 年前

    让我们说一,二。。。是正则表达式:

    让我们重写这些部分:

    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"])
    

    (当然,这也适用于标准单词,由于|部分的原因,仍然值得使用regex)

    现在这是一个伪装的循环,每个术语都被硬编码:

    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]
    

    在性能方面:

    正则表达式是在开始时编译的,因此它尽可能快 没有循环或粘贴的表达式,“或”部分是由regex引擎完成的,这在大多数情况下是一些编译的代码:在纯python中是做不到的。 匹配和拆分在一次操作中完成 最后一个问题是,regex引擎在内部搜索循环中的所有表达式,这使其成为一个O(n)算法。为了加快速度,您必须预测哪个模式最频繁,并将其放在第一位(我的假设是正则表达式是“不相交的”,这意味着一个文本不能由多个匹配,否则最长的必须在较短的之前)

        3
  •  0
  •   colin-zhou GirishB    7 年前

    我花了一些时间调查 re.findall re.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_pattern,string),它将触发对_compile(pattern,flags)的额外调用,在该函数中,它将执行一些检查并在缓存字典中搜索该模式。但是,如果我们打电话 compile_pattern.findall(string) 相反,这种“附加操作”就不存在了。所以 编译模式.findall(字符串) 将比re.findall(编译模式,字符串)快