关于正则表达式:是否值得使用Python的re.compile?

Is it worth using Python's re.compile?

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

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

VS

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


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

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

来自模块re.py(评论是我的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

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


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

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

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

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

与编译:

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

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


FWIW:

1
2
3
4
5
$ 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)完成的,我现在得到以下时间:

1
2
3
4
5
6
7
8
9
10
11
% 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(...),即编译表示的幕后缓存似乎没有发生。


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

1
2
3
4
5
6
7
8
~$ 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:

1
2
3
4
5
6
7
8
~$ 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

因此,即使您只匹配一次,在这个简单的情况下,编译看起来似乎更快。


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

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

但是,检查代码,显示缓存限制为100个表达式。这引出了一个问题,溢出缓存有多痛苦?该代码包含正则表达式编译器的内部接口re.sre_compile.compile。如果我们调用它,我们绕过缓存。对于基本正则表达式,例如r'\w+\s+([0-9_]+)\s+\w*',结果大约慢了两个数量级。

这是我的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#!/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

这是我的机器上的输出:

1
2
3
4
5
6
7
8
9
10
11
$ 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次,而不是一百万次。


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

运行这段代码:

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

与运行此代码相同:

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

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

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

(C)实际上是:

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

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

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

1
2
3
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(...)只被调用一次,我们需要将已编译的正则表达式对象存储在内存中。如果我们使用类,我们可以存储对象并在每次调用函数时重用。

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

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

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

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

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

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

  • George的testInLoop vs编译的2秒差异主要是构建密钥和搜索_cache所需的时间。它并不意味着正则表达式的编译时间。

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

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

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

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

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

    谢谢阅读。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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()会绕过额外的间接和缓存逻辑:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    _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带来的小速度优势之外,人们还喜欢通过命名可能复杂的模式规范并将它们与应用的业务逻辑分离而来的可读性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #### 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时都会重建它们:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    >>> 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文件:

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


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

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

    VS

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

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

    1
    2
    3
    4
    5
    pattern = '''
    hello[ ]world    # Some info on my pattern logic. [ ] to recognize space
    '''


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

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


    使用给定的示例:

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

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

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

    re.compile()返回正则表达式对象,这意味着h是一个正则表达式对象。

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

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

    POS

    The optional second parameter pos gives an index in the string where
    the search is to start; it defaults to 0. This is not completely
    equivalent to slicing the string; the '^' pattern character matches at
    the real beginning of the string and at positions just after a
    newline, but not necessarily at the index where the search is to
    start.

    endpos

    The optional parameter endpos limits how far the string will be
    searched; it will be as if the string is endpos characters long, so
    only the characters from pos to endpos - 1 will be searched for a
    match. If endpos is less than pos, no match will be found; otherwise,
    if rx is a compiled regular expression object, rx.search(string, 0,
    50)
    is equivalent to rx.search(string[:50], 0).

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

    正如您所见,re.match(pattern, string, flags=0)不支持它们,
    它的搜索,findall和finditer也没有。

    匹配对象具有补充这些参数的属性:

    match.pos

    The value of pos which was passed to the search() or match() method of
    a regex object. This is the index into the string at which the RE
    engine started looking for a match.

    match.endpos

    The value of endpos which was passed to the search() or match() method
    of a regex object. This is the index into the string beyond which the
    RE engine will not go.

    正则表达式对象有两个唯一的,可能有用的属性:

    regex.groups

    The number of capturing groups in the pattern.

    regex.groupindex

    A dictionary mapping any symbolic group names defined by (?P) to
    group numbers. The dictionary is empty if no symbolic groups were used
    in the pattern.

    最后,匹配对象具有以下属性:

    match.re

    The regular expression object whose match() or search() method
    produced this match instance.


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

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

    运行1 - 使用re.compile

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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

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

    1
    2
    3
    4
    5
    6
    7
    8
    >>> 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

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

    我有一些调试一些简单代码的痛苦经历:

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

    后来我用比较

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

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

    我有麻烦,patternPhrases与一些预期的字符串不匹配!

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

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

    然后在

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

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

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

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

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


    根据Python文档:

    序列

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

    相当于

    1
    result = re.match(pattern, string)

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

    所以我的结论是,如果你要为许多不同的文本匹配相同的模式,你最好预先编译它。


    除了表现。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #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的详尽备忘单供您参考。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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'   : ['
    ', '\t', '
    ', '\f', '\v'],
              '
    shorthand'       : ['\d', '\w', '\s']},
    '
    methods': {['search', 'match', 'findall', 'finditer'],
                  ['
    split', 'sub']},
    '
    match_object': ['group','groups', 'groupdict','start', 'end', 'span',]
    }

    有趣的是,编译确实对我来说更有效(Win XP上的Python 2.5.2):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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行注释一次,编译后的正则表达式快两倍


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

    这就像编程的一切(实际上是生活中的一切)。运用常识。


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

    Using re.compile makes your code more dynamic, as you can call the already compiled regex, instead of compiling again and aagain. This thing benefits you in cases:

  • 处理器努力
  • 时间复杂性。
  • 使regex Universal。(可用于findall,搜索,匹配)
  • 并使您的程序看起来很酷。
  • 示例:

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

    在Findall中使用

    1
     find_alpha_numeric_string.findall(example_string)

    在搜索中使用

    1
      find_alpha_numeric_string.search(example_string)

    Similarly you can use it for: Match and Substitute


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    """ 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() - > size,hits,nclear,那不是很好吗...


    I've had a lot of experience running a compiled regex 1000s
    of times versus compiling on-the-fly, and have not noticed
    any perceivable difference

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    >>> 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中的示例模拟在导入时创建一个已编译的正则表达式对象,而在匹配时需要"在运行中"。


    作为替代答案,正如我之前未提及的那样,我将继续引用Python 3文档:

    Should you use these module-level functions, or should you get the pattern and call its methods yourself? If you’re accessing a regex within a loop, pre-compiling it will save a few function calls. Outside of loops, there’s not much difference thanks to the internal cache.


    正则表达式在使用第二个版本之前编译。如果你要多次执行它,最好先编译它。如果没有编译每次匹配一个关闭是没关系的。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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()

    在你的申请中,你写道:

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

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

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

    1
    2
    3
    4
    5
    6
    import re

    class Typo:

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

    在申请中:

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

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

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


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

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

    Calling the compiled pattern object's search function with the string 'M' accomplishes the same thing as calling re.search with both the regular expression and the string 'M'. Only much, much faster. (In fact, the re.search function simply compiles the regular expression and calls the resulting pattern object's search method for you.)