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

时间:2009-01-16 21:31:58

标签: python regex

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

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

VS

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

27 个答案:

答案 0 :(得分:381)

我有很多运行编译正则表达式的经验,而不是即时编译,并没有注意到任何可察觉的差异。显然,这是轶事,当然不是反对编译的好论据,但我发现差异可以忽略不计。

编辑: 在快速浏览一下实际的Python 2.5库代码之后,我看到Python无论如何都在内部编译AND CACHES正则表达式(包括对re.match()的调用),所以你真的只是在正则表达式编译时改变,并且不应该节省很多时间 - 只需要检查缓存所需的时间(内部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

我仍然经常预编译正则表达式,但只是将它们绑定到一个漂亮的,可重复使用的名称,而不是任何预期的性能增益。

答案 1 :(得分:114)

对我来说,re.compile的最大好处是能够将正则表达式的定义与其使用分开。

即使是一个简单的表达式,例如0|[1-9][0-9]*(基数为10但没有前导零的整数)也可能足够复杂,您不必重新键入它,检查是否有任何拼写错误,以后必须重新检查如果开始调试时有拼写错误。另外,使用变量名称(如num或num_b10)比使用0|[1-9][0-9]*更好。

当然可以存储字符串并将它们传递给re.match;但是, less 可读:

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

编译:

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

虽然它非常接近,但是当反复使用时,第二行的最后一行感觉更自然,更简单。

答案 2 :(得分:54)

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可能是值得的(特别是对于更复杂的正则表达式)。

反对过早优化的标准论据适用,但如果你怀疑你的正则表达式可能成为性能瓶颈,我认为你使用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(...),即没有幕后缓存编译的表示似乎发生了。

答案 3 :(得分:39)

这是一个简单的测试用例:

~$ 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 loops, best of 3: 3.1 usec per loop
10 loops, best of 3: 2.41 usec per loop
100 loops, best of 3: 2.24 usec per loop
1000 loops, best of 3: 2.21 usec per loop
10000 loops, best of 3: 2.23 usec per loop
100000 loops, best of 3: 2.24 usec per loop
1000000 loops, best of 3: 2.31 usec per loop

使用re.compile:

~$ 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 loops, best of 3: 1.91 usec per loop
10 loops, best of 3: 0.691 usec per loop
100 loops, best of 3: 0.701 usec per loop
1000 loops, best of 3: 0.684 usec per loop
10000 loops, best of 3: 0.682 usec per loop
100000 loops, best of 3: 0.694 usec per loop
1000000 loops, best of 3: 0.702 usec per loop

所以,对于这个简单的情况,即使你只匹配一次似乎编译得更快。

答案 4 :(得分:14)

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

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

但是,检查code表示缓存限制为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次,而不是一百万次。

答案 5 :(得分:10)

我同意诚实的安倍,在给定的例子中match(...)是不同的。它们不是一对一的比较,因此结果各不相同。为了简化我的回复,我使用A,B,C,D来处理这些函数。哦,是的,我们正在处理re.py中的4个函数,而不是3个。

运行这段代码:

h = re.compile('hello')                   # (A)
h.match('hello world')                    # (B)

与运行此代码相同:

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

因为,当查看来源re.py时,(A + B)表示:

h = re._compile('hello')                  # (D)
h.match('hello world')

和(C)实际上是:

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

因此,(C)与(B)不同。实际上,(C)在调用(D)之后调用(B),其也被(A)调用。换句话说,(C) = (A) + (B)。因此,在循环内比较(A + B)与循环内的(C)具有相同的结果。

乔治的regexTest.py证明了这一点。

noncompiled took 4.555 seconds.           # (C) in a loop
compiledInLoop took 4.620 seconds.        # (A + B) in a loop
compiled took 2.323 seconds.              # (A) once + (B) in a loop

每个人的兴趣是,如何获得2.323秒的结果。为了确保只调用compile(...)一次,我们需要将已编译的正则表达式对象存储在内存中。如果我们使用类,我们可以存储对象并在每次调用函数时重用。

class Foo:
    regex = re.compile('hello')
    def my_function(text)
        return regex.match(text)

如果我们不使用课程(这是我今天的要求),那么我没有评论。我还在学习在Python中使用全局变量,我知道全局变量是一件坏事。

还有一点,我相信使用(A) + (B)方法会占上风。以下是我观察到的一些事实(如果我错了,请纠正我):

  1. 调用一次,它将在_cache中执行一次搜索,然后执行一次sre_compile.compile()以创建正则表达式对象。调用A两次,它将执行两次搜索和一次编译(因为正则表达式对象被缓存)。

  2. 如果_cache之间被刷新,则regex对象从内存中释放,Python需要再次编译。 (有人建议Python不会重新编译。)

  3. 如果我们使用(A)保留正则表达式对象,则正则表达式对象仍将进入_cache并以某种方式刷新。但是我们的代码会对它进行引用,并且regex对象不会从内存中释放出来。那些,Python不需要再次编译。

  4. George的testInLoop vs编译测试中的2秒差异主要是构建密钥和搜索_cache所需的时间。它并不代表正则表达式的编译时间。

  5. George的真正的编译测试显示了如果它每次真正重新编译会发生什么:它会慢100倍(他将循环从1,000,000减少到10,000)。

  6. 以下是(A + B)优于(C)的唯一情况:

    1. 如果我们可以在类中缓存正则表达式对象的引用。
    2. 如果我们需要重复调​​用(B)(在循环内或多次调用),我们必须在循环外缓存对regex对象的引用。
    3. (C)足够好的情况:

      1. 我们无法缓存参考。
      2. 我们偶尔会使用它。
      3. 总的来说,我们没有太多的正则表达式(假设编译后的版本永远不会被刷新)
      4. 回顾一下,这是A B C:

        h = re.compile('hello')                   # (A)
        h.match('hello world')                    # (B)
        re.match('hello', 'hello world')          # (C)
        

        感谢阅读。

答案 6 :(得分:7)

大多数情况下,您是否使用 re.compile 没有什么区别。在内部,所有函数都是在编译步骤中实现的:

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

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

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

def sub(pattern, repl, string, count=0, flags=0):
    return _compile(pattern, flags).sub(repl, string, count)

def subn(pattern, repl, string, count=0, flags=0):
    return _compile(pattern, flags).subn(repl, string, count)

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

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

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

另外,re.compile()会绕过额外的间接和缓存逻辑:

_cache = {}

_pattern_type = type(sre_compile.compile("", 0))

_MAXCACHE = 512
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.compile 带来的小速度优势之外,人们还喜欢通过命名可能复杂的模式规范并将其与应用的业务逻辑分离而产生的可读性: p>

#### Patterns ############################################################
number_pattern = re.compile(r'\d+(\.\d*)?')    # Integer or decimal number
assign_pattern = re.compile(r':=')             # Assignment operator
identifier_pattern = re.compile(r'[A-Za-z]+')  # Identifiers
whitespace_pattern = re.compile(r'[\t ]+')     # Spaces and tabs

#### Applications ########################################################

if whitespace_pattern.match(s): business_logic_rule_1()
if assign_pattern.match(s): business_logic_rule_2()

注意,另一位受访者错误地认为 pyc 文件直接存储了编译模式;但是,实际上每次加载PYC时都会重建它们:

>>> from dis import dis
>>> with open('tmp.pyc', 'rb') as f:
        f.read(8)
        dis(marshal.load(f))

  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (None)
              6 IMPORT_NAME              0 (re)
              9 STORE_NAME               0 (re)

  3          12 LOAD_NAME                0 (re)
             15 LOAD_ATTR                1 (compile)
             18 LOAD_CONST               2 ('[aeiou]{2,5}')
             21 CALL_FUNCTION            1
             24 STORE_NAME               2 (lc_vowels)
             27 LOAD_CONST               1 (None)
             30 RETURN_VALUE

上面的反汇编来自tmp.py的PYC文件,其中包含:

import re
lc_vowels = re.compile(r'[aeiou]{2,5}')

答案 7 :(得分:6)

在此示例中,使用re.compile的速度是requested的50倍以上。

这一点与我在上面的注释中所提到的相同,即,当您的用法无法从编译缓存中获得太多好处时,使用re.compile可能是一个重要的优势。至少在一种特定情况下会发生这种情况(我在实践中遇到过这种情况),即以下所有条件都成立时:

  • 您有很多正则表达式模式(超过re._MAXCACHE,其default当前为512),
  • 您经常使用这些正则表达式,并且
  • 相同模式的连续用法之间有不止re._MAXCACHE个其他正则表达式,因此在两次连续用法之间,每个正则表达式都会从缓存中清除。
import re
import time

def setup(N=1000):
    # Patterns 'a.*a', 'a.*b', ..., 'z.*z'
    patterns = [chr(i) + '.*' + chr(j)
                    for i in range(ord('a'), ord('z') + 1)
                    for j in range(ord('a'), ord('z') + 1)]
    # If this assertion below fails, just add more (distinct) patterns.
    # assert(re._MAXCACHE < len(patterns))
    # N strings. Increase N for larger effect.
    strings = ['abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'] * N
    return (patterns, strings)

def without_compile():
    print('Without re.compile:')
    patterns, strings = setup()
    print('searching')
    count = 0
    for s in strings:
        for pat in patterns:
            count += bool(re.search(pat, s))
    return count

def without_compile_cache_friendly():
    print('Without re.compile, cache-friendly order:')
    patterns, strings = setup()
    print('searching')
    count = 0
    for pat in patterns:
        for s in strings:
            count += bool(re.search(pat, s))
    return count

def with_compile():
    print('With re.compile:')
    patterns, strings = setup()
    print('compiling')
    compiled = [re.compile(pattern) for pattern in patterns]
    print('searching')
    count = 0
    for s in strings:
        for regex in compiled:
            count += bool(regex.search(s))
    return count

start = time.time()
print(with_compile())
d1 = time.time() - start
print(f'-- That took {d1:.2f} seconds.\n')

start = time.time()
print(without_compile_cache_friendly())
d2 = time.time() - start
print(f'-- That took {d2:.2f} seconds.\n')

start = time.time()
print(without_compile())
d3 = time.time() - start
print(f'-- That took {d3:.2f} seconds.\n')

print(f'Ratio: {d3/d1:.2f}')

我在笔记本电脑上获得的示例输出(Python 3.7.7):

With re.compile:
compiling
searching
676000
-- That took 0.33 seconds.

Without re.compile, cache-friendly order:
searching
676000
-- That took 0.67 seconds.

Without re.compile:
searching
676000
-- That took 23.54 seconds.

Ratio: 70.89

我并没有为timeit而烦恼,因为两者之间的差异是如此明显,但是每次得到的定性数字都差不多。请注意,即使没有re.compile,也可以多次使用相同的正则表达式并转到下一个(不是re.compile的2倍),而是以另一种顺序(循环通过许多正则表达式),这比预期的要差得多。另外,增加缓存大小也可以:仅在上面的re._MAXCACHE = len(patterns)中设置setup()(当然,我不建议在生产环境中进行此类操作,因为带下划线的名称通常是“私有”的)会降低〜23秒减少到约0.7秒,这也符合我们的理解。

答案 8 :(得分:5)

一般来说,我发现使用标记(至少更容易记住)更容易,比如编译模式时re.I而不是内联标记。

>>> foo_pat = re.compile('foo',re.I)
>>> foo_pat.findall('some string FoO bar')
['FoO']

vs

>>> re.findall('(?i)foo','some string FoO bar')
['FoO']

答案 9 :(得分:4)

使用re.compile()有一个额外的好处,就是使用re.VERBOSE为我的正则表达式模式添加注释

pattern = '''
hello[ ]world    # Some info on my pattern logic. [ ] to recognize space
'''

re.search(pattern, 'hello world', re.VERBOSE)

虽然这不会影响代码的运行速度,但我喜欢这样做,因为它是我评论习惯的一部分。我完全不喜欢花时间试图记住我想要修改后2个月后代码背后的逻辑。

答案 10 :(得分:4)

使用给定的例子:

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

上例中的匹配方法与下面使用的方法不同:

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

re.compile()返回regular expression object,表示h是正则表达式对象。

正则表达式对象有自己的match方法,带有可选的 pos endpos 参数:

regex.match(string[, pos[, endpos]])

<强>正

  

可选的第二个参数 pos 在字符串中给出一个索引   搜索是要开始的;它默认为0.这不完全   相当于切割字符串; '^'模式字符匹配于   字符串的真正开始和刚好在a之后的位置   换行,但不一定在搜索所在的索引处   启动。

<强> endpos

  

可选参数 endpos 限制字符串的范围   搜查;它就好像字符串 endpos 字符长,所以   只会搜索 pos endpos - 1中的字符   比赛。如果 endpos 小于 pos ,则不会找到匹配项;除此以外,   如果 rx 是已编译的正则表达式对象,rx.search(string, 0, 50)等同于rx.search(string[:50], 0)

正则表达式对象的搜索 findall finditer 方法也支持这些参数。

正如您所见,

re.match(pattern, string, flags=0)不支持他们,
它的搜索 findall finditer 对应物也不存在。

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

<强> match.pos

  

传递给search()或match()方法的pos的值   正则表达式对象。这是RE的字符串索引   引擎开始寻找比赛。

<强> match.endpos

  

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


regex object有两个独特的,可能有用的属性:

<强> regex.groups

  

模式中捕获组的数量。

<强> regex.groupindex

  

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


最后,match object有这个属性:

<强> match.re

  

正则表达式对象,其match()或search()方法   产生了这个匹配实例。

答案 11 :(得分:3)

我在这里讨论之前遇到了这个测试。然而,运行它我认为我至少会发布我的结果。

我在Jeff Friedl的“掌握正则表达式”中偷走并混淆了这个例子。这是在运行OSX 10.6(2Ghz intel core 2 duo,4GB ram)的macbook上。 Python版本是2.6.1。

运行1 - 使用re.compile

import re 
import time 
import fpformat
Regex1 = re.compile('^(a|b|c|d|e|f|g)+$') 
Regex2 = re.compile('^[a-g]+$')
TimesToDo = 1000
TestString = "" 
for i in range(1000):
    TestString += "abababdedfg"
StartTime = time.time() 
for i in range(TimesToDo):
    Regex1.search(TestString) 
Seconds = time.time() - StartTime 
print "Alternation takes " + fpformat.fix(Seconds,3) + " seconds"

StartTime = time.time() 
for i in range(TimesToDo):
    Regex2.search(TestString) 
Seconds = time.time() - StartTime 
print "Character Class takes " + fpformat.fix(Seconds,3) + " seconds"

Alternation takes 2.299 seconds
Character Class takes 0.107 seconds

运行2 - 不使用re.compile

import re 
import time 
import fpformat

TimesToDo = 1000
TestString = "" 
for i in range(1000):
    TestString += "abababdedfg"
StartTime = time.time() 
for i in range(TimesToDo):
    re.search('^(a|b|c|d|e|f|g)+$',TestString) 
Seconds = time.time() - StartTime 
print "Alternation takes " + fpformat.fix(Seconds,3) + " seconds"

StartTime = time.time() 
for i in range(TimesToDo):
    re.search('^[a-g]+$',TestString) 
Seconds = time.time() - StartTime 
print "Character Class takes " + fpformat.fix(Seconds,3) + " seconds"

Alternation takes 2.508 seconds
Character Class takes 0.109 seconds

答案 12 :(得分:3)

这个答案可能会迟到,但却是一个有趣的发现。如果您计划多次使用正则表达式,使用编译可以真正节省您的时间(这也在文档中提到)。下面你可以看到,当直接调用match方法时,使用编译的正则表达式是最快的。将已编译的正则表达式传递给re.match会使速度更慢,并且将带有模式字符串的re.match传递到中间位置。

>>> 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

答案 13 :(得分:3)

除了性能差异之外,使用re.compile并使用编译的正则表达式对象进行匹配(无论正则表达式相关的操作)使得语义更清晰于Python运行时。

我在调试一些简单代码方面遇到了一些痛苦的经历:

compare = lambda s, p: re.match(p, s)

以后我会在

中使用compare
[x for x in data if compare(patternPhrases, x[columnIndex])]

其中patternPhrases应该是包含正则表达式字符串的变量,x[columnIndex]是包含字符串的变量。

我遇到patternPhrases与某些预期字符串不匹配的问题!

但是如果我使用re.compile形式:

compare = lambda s, p: p.match(s)

然后在

[x for x in data if compare(patternPhrases, x[columnIndex])]

Python会抱怨“字符串没有匹配属性”,因为compare中的位置参数映射,x[columnIndex]被用作正则表达式!,当我实际意味着

compare = lambda p, s: p.match(s)

在我的情况下,使用re.compile更明确的是正则表达式的目的,当它的值被肉眼隐藏时,因此我可以从Python运行时检查获得更多帮助。

所以我的教训是,当正则表达式不仅仅是文字字符串时,我应该使用re.compile让Python帮我断言我的假设。

答案 14 :(得分:3)

有趣的是,编译确实对我来说更有效(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行注释一遍,编译的正则表达式快两倍

答案 15 :(得分:3)

除了表演。

使用compile帮助我区分
的概念 1。模块(重新)
2。正则表达式对象
3。匹配对象
当我开始学习正则表达式时

#regex object
regex_object = re.compile(r'[a-zA-Z]+')
#match object
match_object = regex_object.search('1.Hello')
#matching content
match_object.group()
output:
Out[60]: 'Hello'
V.S.
re.search(r'[a-zA-Z]+','1.Hello').group()
Out[61]: 'Hello'

作为补充,我制作了模块re的详尽备忘单供您参考。

regex = {
'brackets':{'single_character': ['[]', '.', {'negate':'^'}],
            'capturing_group' : ['()','(?:)', '(?!)' '|', '\\', 'backreferences and named group'],
            'repetition'      : ['{}', '*?', '+?', '??', 'greedy v.s. lazy ?']},
'lookaround' :{'lookahead'  : ['(?=...)', '(?!...)'],
            'lookbehind' : ['(?<=...)','(?<!...)'],
            'caputuring' : ['(?P<name>...)', '(?P=name)', '(?:)'],},
'escapes':{'anchor'          : ['^', '\b', '$'],
          'non_printable'   : ['\n', '\t', '\r', '\f', '\v'],
          'shorthand'       : ['\d', '\w', '\s']},
'methods': {['search', 'match', 'findall', 'finditer'],
              ['split', 'sub']},
'match_object': ['group','groups', 'groupdict','start', 'end', 'span',]
}

答案 16 :(得分:2)

根据Python documentation

序列

prog = re.compile(pattern)
result = prog.match(string)

等同于

result = re.match(pattern, string)

但是,如果在单个程序中多次使用该表达式,则使用re.compile()并保存生成的正则表达式对象以供重用会更有效。

所以我的结论是,如果您要为许多不同的文本匹配相同的模式,则最好对其进行预编译。

答案 17 :(得分:2)

我真的很尊重上述所有答案。从我的意见 是!肯定值得使用re.compile而不是每次都一次又一次地编译正则表达式。

  

使用 re.compile 可以使您的代码更具动态性,因为您可以调用已编译的正则表达式,而不是再次编译和重新编译。在以下情况下,这件事会让您受益:

  1. 处理器工作
  2. 时间复杂性。
  3. 制作正则表达式通用。(可用于查找,搜索,匹配)
  4. 让您的程序看起来很酷。
  5. 示例:

      example_string = "The room number of her room is 26A7B."
      find_alpha_numeric_string = re.compile(r"\b\w+\b")
    

    在Findall中使用

     find_alpha_numeric_string.findall(example_string)
    

    在搜索中使用

      find_alpha_numeric_string.search(example_string)
    
      
        

    同样,您可以将其用于:匹配和替换

      

答案 18 :(得分:2)

这是一个很好的问题。你经常看到人们毫无理由地使用re.compile。它降低了可读性。但是肯定有很多次需要预编译表达式。就像你在循环中重复使用它一样或者某些东西。

就像编程一切(生活中的一切)。运用常识。

答案 19 :(得分:1)

  

我在运行已编译的正则表达式1000s方面有很多经验   时间与在线编译,并没有注意到   任何可感知的差异

对已接受答案的投票导致假设@Triptych所说的对所有情况都是正确的。这不一定是真的。一个很大的区别是当你必须决定是接受正则表达式字符串还是编译的正则表达式对象作为函数的参数时:

>>> timeit.timeit(setup="""
... import re
... f=lambda x, y: x.match(y)       # accepts compiled regex as parameter
... h=re.compile('hello')
... """, stmt="f(h, 'hello world')")
0.32881879806518555
>>> timeit.timeit(setup="""
... import re
... f=lambda x, y: re.compile(x).match(y)   # compiles when called
... """, stmt="f('hello', 'hello world')")
0.809190034866333

编译正则表达式总是更好,以防您需要重用它们。

注意上面timeit中的示例模拟在导入时创建一个已编译的正则表达式对象,而不是&#34; on-the-fly&#34;当需要比赛时。

答案 20 :(得分:1)

(几个月后)很容易在re.match周围添加自己的缓存, 或其他任何事情 -

""" Re.py: Re.match = re.match + cache  
    efficiency: re.py does this already (but what's _MAXCACHE ?)
    readability, inline / separate: matter of taste
"""

import re

cache = {}
_re_type = type( re.compile( "" ))

def match( pattern, str, *opt ):
    """ Re.match = re.match + cache re.compile( pattern ) 
    """
    if type(pattern) == _re_type:
        cpat = pattern
    elif pattern in cache:
        cpat = cache[pattern]
    else:
        cpat = cache[pattern] = re.compile( pattern, *opt )
    return cpat.match( str )

# def search ...

一个wibni,如果:cachehint(size =),cacheinfo() - &gt;是不是很好。大小,命中,明确...

答案 21 :(得分:0)

尽管这两种方法在速度方面具有可比性,但您应该知道,如果您要处理数百万次迭代,仍然存在一些可以忽略不计的时间差异。 >

以下速度测试:

import re
import time

SIZE = 100_000_000

start = time.time()
foo = re.compile('foo')
[foo.search('bar') for _ in range(SIZE)]
print('compiled:  ', time.time() - start)

start = time.time()
[re.search('foo', 'bar') for _ in range(SIZE)]
print('uncompiled:', time.time() - start)

给出以下结果:

compiled:   14.647532224655151
uncompiled: 61.483458042144775

编译方法在我的 PC(使用 Python 3.7.0)上始终快 4 倍。

documentation 中所述:

<块引用>

如果您在循环中访问正则表达式,预编译它会节省一些函数调用。在循环之外,由于内部缓存,没有太大区别。

答案 22 :(得分:0)

易读性/认知负荷偏好

对我来说,主要收获是,我只需要记住并阅读复​​杂的正则表达式API语法的一种形式-<compiled_pattern>.method(xxx)形式而不是 re.func(<pattern>, xxx)表单。

re.compile(<pattern>)有点多余,的确如此。

但是在正则表达式方面,额外的编译步骤不太可能是造成认知负担的主要原因。实际上,在复杂的模式上,您甚至可以通过将声明与随后在其上调用的任何regex方法分开来获得清晰度。

我倾向于首先在Regex101之类的网站中甚至在一个单独的最小测试脚本中调整复杂的模式,然后将它们引入我的代码中,因此将声明与使用分离开也适合我的工作流程。

答案 23 :(得分:0)

正则表达式在使用第二个版本之前被编译。如果你要多次执行它,最好先编译它。如果不是每次匹配时都没有编译就可以了。

答案 24 :(得分:0)

作为替代答案,我发现以前没有提到过,我将继续引用Python 3 docs

  

您应该使用这些模块级函数,还是应该自己获取模式并调用其方法?如果您要在循环中访问正则表达式,则对其进行预编译将节省一些函数调用。在循环之外,由于内部缓存,差异不大。

答案 25 :(得分:-1)

我想激励预编译在概念上和“文明”(如“文学编程”)中都是有利的。看看这段代码:

from re import compile as _Re

class TYPO:

  def text_has_foobar( self, text ):
    return self._text_has_foobar_re_search( text ) is not None
  _text_has_foobar_re_search = _Re( r"""(?i)foobar""" ).search

TYPO = TYPO()

在你的申请中,你写道:

from TYPO import TYPO
print( TYPO.text_has_foobar( 'FOObar ) )

就功能而言,这一点非常简单。因为这是一个很短的例子,我把在一行中得到_text_has_foobar_re_search的方式混为一谈。这段代码的缺点是,无论TYPO库对象的生命周期是什么,它都会占用一点内存;优点是,当进行foobar搜索时,您将获得两个函数调用和两个类字典查找。 re缓存了多少个正则表达式,并且缓存的开销与此无关。

将此与更常见的风格进行比较,如下所示:

import re

class Typo:

  def text_has_foobar( self, text ):
    return re.compile( r"""(?i)foobar""" ).search( text ) is not None

在申请中:

typo = Typo()
print( typo.text_has_foobar( 'FOObar ) )

我欣然承认我的风格对于python非常不寻常,甚至可能引起争议。但是,在更接近匹配python如何使用的示例中,为了进行单个匹配,我们必须实例化一个对象,执行三个实例字典查找,并执行三个函数调用;另外,当使用超过100个正则表达式时,我们可能会遇到re缓存问题。另外,正则表达式隐藏在方法体内,大部分时间都不是一个好主意。

不管怎么说,每个措施的子集---有针对性,别名的进口报表;适用的别名方法;减少函数调用和对象字典查找---可以帮助减少计算和概念的复杂性。

答案 26 :(得分:-5)

我的理解是这两个例子实际上是等价的。唯一的区别是,在第一个中,您可以在其他地方重用已编译的正则表达式,而不会再次编译它。

以下是您的参考:http://diveintopython3.ep.io/refactoring.html

  

使用字符串'M'调用已编译模式对象的搜索函数与使用正则表达式和字符串'M'调用re.search完成相同的操作。只有更多,更快。 (实际上,re.search函数只是编译正则表达式并为您调用生成的模式对象的搜索方法。)