值得使用python的re.compile吗?

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

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

vs

re.match('hello', 'hello world')
390499 次浏览

使用第二个版本时,正则表达式在使用之前会进行编译。如果你要多次执行它,最好先编译它。如果不是每次编译都匹配一次性的是好的。

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

这里有一个参考:http://diveintopython3.ep.io/refactoring.html

使用字符串'M'调用已编译模式对象的搜索函数,其效果与同时使用正则表达式和字符串'M'调用re.search相同。只是要快得多。(事实上,re.search函数只是编译正则表达式,并为您调用结果模式对象的搜索方法。)

就其价值而言:

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

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

我有很多运行一个编译过的正则表达式和实时编译的经验,并没有注意到任何可感知的差异。显然,这是轶事,当然不是反对编译的一个很好的参数,但我发现差异可以忽略不计。

< p >编辑: 在快速浏览了实际的Python 2.5库代码后,我发现无论何时你使用正则表达式(包括调用re.match()), Python都会在内部编译和缓存正则表达式,所以你实际上只在正则表达式被编译时进行更改,并且不应该节省太多时间——只节省检查缓存所需的时间(在内部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

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

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

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

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

即使是一个简单的表达式,如0|[1-9][0-9]*(以10为基数的整数,没有前导零),也可能非常复杂,以至于您宁愿不重新输入它,检查是否有任何拼写错误,然后在开始调试时重新检查是否有拼写错误。另外,使用像num或num_b10这样的变量名比使用0|[1-9][0-9]*更好。

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

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

与编译:

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

虽然它很接近,但当重复使用时,第二句的最后一行感觉更自然、更简单。

有趣的是,编译对我来说确实更有效(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,则编译后的正则表达式的速度将提高一倍

一般来说,我发现在编译模式时使用标记比内联使用标记更容易(至少更容易记住如何使用),比如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']

(几个月后)很容易在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() -> size, hits, nclear…

在无意中看到这里的讨论之前,我运行了这个测试。然而,在运行它之后,我想我至少会发布我的结果。

我剽窃了Jeff Friedl的“精通正则表达式”中的例子。这是在一台运行OSX 10.6 (2Ghz英特尔酷睿2双核,4GB内存)的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

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

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

然而,检查代码,发现缓存被限制在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次,而不是一百万次。

我想说的是,预编译在概念上和“字面上”(如在“文学编程”中)都是有利的。看看这段代码片段:

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缓存问题。此外,正则表达式被隐藏在方法体中,这在大多数情况下并不是一个好主意。

可以说,每一个措施的子集——有针对性的,别名的import语句;别名方法(如适用);减少函数调用和对象字典查找——可以帮助减少计算和概念的复杂性。

下面是一个简单的测试用例:

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

因此,使用简单的情况即使你只匹配一次编译似乎更快。

用下面的例子:

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

上面例子中的匹配方法与下面使用的方法不一样:

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

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

regex对象有自己的< em >与< / em >方法,带有可选的posendpos参数:

# EYZ1

pos

第二个可选参数pos给出了字符串中的一个索引 搜寻就要开始了;缺省值为0。这并不完全是 相当于对字符串进行切片;'^'模式字符匹配在 字符串的真正开始和在a之后的位置 换行符,但不一定在搜索到的索引处 开始。< / p >

endpos

可选参数endpos限制了字符串的长度 搜索;它会像字符串是endpos字符长,所以 只搜索从posendpos - 1的字符 匹配。如果endpos小于pos,将找不到匹配;否则, 如果处方是一个编译后的正则表达式对象,rx。搜索(字符串,0, 50)等价于rx.search(string[:50], 0)

regex对象的搜索findall发现方法也支持这些参数。

如你所见,< p>re.match(pattern, string, flags=0)不支持它们,
它的搜索findall发现也没有

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

match.pos

的search()或match()方法传递给pos的值 一个正则表达式对象。这是正则表达式所在字符串的索引

.引擎开始寻找匹配

match.endpos

传递给search()或match()方法的endpos值 正则表达式对象的。对象超出的字符串的索引

. RE引擎将不运行

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

regex.groups

模式中捕获组的数量。

regex.groupindex

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

.在模式中

最后,匹配对象有这个属性:

match.re

使用match()或search()方法的正则表达式对象

抛开性能差异不考虑,使用re.compile和使用编译后的正则表达式对象进行匹配(任何与正则表达式相关的操作)使得Python运行时的语义更加清晰。

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

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

然后我用compare in

[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帮助我断言我的假设。

我同意Honest Abe的观点,在给出的例子中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), (D)也被(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. 调用A一次,它将在_cachesre_compile.compile()中进行一次搜索,以创建一个正则表达式对象。调用A两次,它将执行两次搜索和一次编译(因为regex对象被缓存)。

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

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

  4. George的测试编译循环和编译的2秒的差异主要是构建键和搜索_cache所需的时间。它并不意味着regex的编译时间。

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

以下是(A + B)比(C)更好的情况:

  1. 如果可以在类中缓存regex对象的引用。
  2. 如果需要重复调用(B)(在循环内或多次),则必须在循环外缓存对regex对象的引用。

如果(C)足够好:

  1. 不能缓存引用。
  2. 我们只是偶尔用一次。
  3. 总的来说,我们没有太多的正则表达式(假设编译后的正则表达式永远不会被刷新)

简单回顾一下,以下是abc:

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

感谢阅读。

使用re.compile()还有一个额外的好处,即使用re.VERBOSE向正则表达式模式添加注释

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


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

虽然这不会影响代码的运行速度,但我喜欢这样做,因为这是我注释习惯的一部分。当我想要修改代码时,我完全不喜欢花时间去记住代码背后的逻辑。

这个答案可能姗姗来迟,但却是一个有趣的发现。如果你打算多次使用regex,使用compile真的可以节省你的时间(这在文档中也有提到)。下面你可以看到,当直接调用match方法时,使用编译后的正则表达式是最快的。将一个编译好的正则表达式传递给re.match会使它更慢,而将re.match与patter字符串传递在中间的某个地方。

>>> 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
我有很多运行编译过的regex 1000的经验 与实时编译相比,并没有注意到 任何可感知的差异

对已接受答案的投票导致假设@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中的示例模拟在导入时一次创建已编译的regex对象,而不是在需要匹配时“动态”创建。

大多数情况下,使用re.compile和不使用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带来的小速度好处外,人们还喜欢命名潜在复杂的模式规范并将其与应用的业务逻辑分离所带来的可读性:

#### 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加载时,它们都会被重新构建:

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

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

import re
lc_vowels = re.compile(r'[aeiou]{2,5}')
我真的很尊重上面所有的答案。在我看来 是的!当然,使用re.compile而不是一次又一次地编译正则表达式是值得的。< / p >

使用re.compile使您的代码更加动态,因为您可以调用已经编译的正则表达式,而不是一次又一次地编译。这对你有好处:

  1. 处理器的努力
  2. 时间复杂度。
  3. 使正则表达式通用。(可以在findall, search, match中使用)
  4. 并使您的程序看起来很酷。

< em >例子:< / em >

  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)

类似地,您可以使用它:匹配和替补

除了表演。

使用compile可以帮助我区分
的概念 # EYZ0, < br > # EYZ0 < br > # EYZ0 < br > 当我开始学习正则表达式

#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',]
}

根据Python 文档:

序列

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

等于

result = re.match(pattern, string)

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

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

作为一个替代答案,正如我看到之前没有提到的,我将继续引用Python 3文档:

您是应该使用这些模块级函数,还是应该获取模式并自己调用它的方法?如果在循环中访问正则表达式,预编译它将节省一些函数调用。在循环之外,由于内部缓存,没有太大区别。

易读性/认知负荷偏好

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

re.compile(<pattern>)是一个额外的样板,真的。

但是考虑到正则表达式,额外的编译步骤不太可能是认知负荷的主要原因。事实上,对于复杂的模式,您甚至可以通过将声明与随后对其调用的任何regex方法分开来获得清晰性。

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

下面是一个使用re.compile比使用要求快50多倍的例子。

这一点与我在上面的评论中所说的是一样的,即,当您的使用从编译缓存中获益不多时,使用re.compile可能是一个显著的优势。这种情况至少发生在一个特定的情况下(我在实践中遇到过),即当以下所有情况都成立时:

  • 您有很多正则表达式模式(超过re._MAXCACHE,其默认的目前是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倍),但在另一个顺序(遍历许多正则表达式)中,情况明显更糟,正如预期的那样。此外,增加缓存大小也可以:简单地在上面的setup()中设置re._MAXCACHE = len(patterns)(当然我不建议在生产中做这样的事情,因为带下划线的名字通常是“私有的”)将~23秒降低到~0.7秒,这也符合我们的理解。

尽管这两种方法在速度上是可以比较的,但是您应该知道,如果您正在处理数百万次迭代,那么仍然存在一些可以忽略不计的时间差。

以下速度测试:

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

正如在文档中所解释的:

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