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

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

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

VS

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

我已经有了很多经验,编译正则expression式1000倍,而不是编译即时,并没有注意到任何可感知的差异。 显然,这是一个轶事,当然不是一个反对编译的伟大论据,但我发现这个差别是微不足道的。

编辑︰快速浏览一下实际的Python 2.5库代码后,我看到,无论如何,无论如何使用它们(包括调用re.match() ),Python内部编译和CACHES正则expression式,所以你真的只是改变当正则expression式编译,并不应该节省很多时间 – 只有检查caching(一个内部dicttypes的关键查找)所需的时间。

从模块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 

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

对我来说, re.compile最大的好处不是任何一种过早的优化( 反正它是万恶之源 )。 它能够将正则expression式的定义与它的使用分开。

即使是一个简单的expression式,例如0|[1-9][0-9]* (以10为底的整数,没有前导零)可能会非常复杂,您不必重新键入它,检查是否有任何拼写错误,并且稍后在开始debugging时必须重新检查是否有拼写错误。 另外,使用诸如num或num_b10之类的variables名称比0|[1-9][0-9]*更好。

存储string并将它们传递给re.match当然是可能的; 然而,这是不太可读:

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

与编译:

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

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

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 

所以,如果你打算使用相同的正则expression式很多,它可能是值得做的re.compile (特别是对于更复杂的正则expression式)。

反对过早优化的标准论据适用,但是如果您怀疑您的正则expression式可能会成为性能瓶颈,我认为使用re.compile并不会真正失去清晰度/直接性。

这是一个简单的testing用例:

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

所以,这个简单的例子看起来编译速度更快, 即使你只匹配一次

我只是自己试了一下 对于从string中parsing数字并对其进行求和的简单情况,使用编译的正则expression式对象大约是使用re方法的两倍。

正如其他人指出的那样, re方法(包括re.compile )在先前编译的expression式的caching中查找正则expression式string。 因此,在正常情况下,使用re方法的额外成本仅仅是caching查找的成本。

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

这是我的testing:

 #!/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 

“真编译”方法使用绕过caching的内部接口。 注意在每个循环迭代上编译的仅迭代10,000次,而不是一百万次。

我同意安倍诚实,在给定的例子中match(...)是不同的。 它们不是一对一的比较,因此结果是不一样的。 为了简化我的回答,我使用A,B,C,D来表示这些function。 哦,是的,我们正在处理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)的结果是相同的。

George的regexTest.py为我们certificate了这一点。

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

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

如果我们不使用class级(这是我今天的要求),那么我没有评论。 我仍然学习在Python中使用全局variables,我知道全局variables是一件坏事。

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

  1. 调用一次,它将在_cache执行一次search,然后再执行一次sre_compile.compile()来创build一个正则expression式对象。 调用两次,它会做两个search和一个编译(因为正则expression式对象被caching)。

  2. 如果_cache被刷新,则正则expression式对象从内存中释放,Python需要重新编译。 (有人build议Python不要重新编译。)

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

  4. 乔治的testingcompileInLoop与编译2秒的差异主要是build立密钥和search_cache所需的时间。 这并不意味着正则expression式的编译时间。

  5. George的真正的编译testing表明,如果真的每次都重新编译,会发生什么:它会慢100倍(他将循环从100万减less到10,000)。

这是(A + B)比(C)好的唯一情况:

  1. 如果我们可以在类中caching正则expression式对象的引用。
  2. 如果我们需要重复调​​用(B)(在一个循环内或多次),我们必须caching循环外的regex对象的引用。

(C)足够好的情况:

  1. 我们无法caching参考。
  2. 我们只是偶尔使用它。
  3. 总的来说,我们没有太多的正则expression式(假设编译的过程不会被刷新)

简要回顾一下,这里是ABC:

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

谢谢阅读。

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

有趣的是,编译对我来说确实更有效率(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行一次评论相反,编译正则expression式是两倍的速度

我在这里进行讨论之前,先进行了这个testing。 然而,运行它,我想我至less会发布我的结果。

我在杰夫·弗里德尔的“掌握正则expression式”中偷走了这个例子。 这是在运行OSX 10.6(2Ghz英特尔酷睿2双核,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('^[ag]+$') 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('^[ag]+$',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 

使用给出的例子:

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

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

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

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

正则expression式对象有自己的匹配方法,可选posendpos参数:

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

POS

可选的第二个参数pos在search要开始的string中给出一个索引; 它默认为0.这不完全等同于切分string; '^'模式字符匹配string的实际开始处和紧跟在换行符之后的位置,但不一定位于search开始的索引处。

endpos

可选参数endpos限制string将被search多远; 就好像string是endpos字符一样长,所以只有从posendpos - 1的字符才会被search到匹配。 如果endpos小于pos ,则不会find匹配; 否则,如果rx是编译的正则expression式对象,则rx.search(string, 0, 50)rx.search(string, 0, 50)等同于rx.search(string[:50], 0)

正则expression式对象的searchfindallfinditer方法也支持这些参数。

你可以看到, re.match(pattern, string, flags=0)不支持它们,
也没有searchfindallfinditer对应。

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

match.pos

传递给正则expression式对象的search()或match()方法的pos的值。 这是RE引擎开始寻找匹配的string的索引。

match.endpos

传递给正则expression式对象的search()或match()方法的endpos的值。 这是RE引擎不会去的string的索引。


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

regex.groups

模式中的捕获组的数量。

regex.groupindex

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


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

match.re

match()或search()方法生成此匹配实例的正则expression式对象。

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

 pattern = ''' hello[ ]world # Some info on my pattern logic. [ ] to recognize space ''' re.search(pattern, 'hello world', re.VERBOSE) 

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

这是一个很好的问题。 你经常看到有人没有理由地使用re.compile。 它减less了可读性。 但是确定有很多时候需要预编译expression式。 就像你在一个循环或者其他一些循环中重复使用它一样。

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

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

我有一些痛苦的经验debugging一些简单的代码:

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

后来我会用比较

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

其中patternPhrases应该是一个包含正则expression式string的variables, x[columnIndex]是一个包含string的variables。

我有麻烦, patternPhrases不匹配一些预期的string!

但是,如果我使用了re.compile表单:

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

然后进入

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

Python会抱怨说“string没有匹配的属性”,因为compare的位置参数映射, x[columnIndex]用作正则expression式!

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

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

所以,我的教训是,当正则expression式不只是字面string,那么我应该使用re.compile让Python来帮助我断言我的假设。

这个答案可能会迟到,但是一个有趣的发现。 如果你计划多次使用正则expression式,使用compile可以真正节省你的时间(这在文档中也有提到)。 下面你可以看到,直接调用匹配方法时,使用编译正则expression式是最快的。 传递一个编译正则expression式re.match使得它甚至更慢,传递与patterstringre.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 

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

 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(),则会为额外的间接性和caching逻辑的开销旁路一些额外开销:

 _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文件直接存储了编译模式; 然而,实际上,每当PYC装载时,它们都被重build:

 >>> 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文件, tmp.py包含:

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

(几个月后)很容易添加你自己的cachingre.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() – >大小,命中,nclear …

我真的尊重所有上述的答案。 从我的意见是的! 当然,使用re.compile是值得的,而不是每次编译正则expression式。

使用re.compile可以使你的代码更具dynamic性,因为你可以调用已经编译好的正则expression式,而不是再次编译和重新编译。 这件事情在以下情况下有好处:

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

例如:

  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) 

在search中使用

  find_alpha_numeric_string.search(example_string) 

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

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

我希望激发预编译在概念上和“文化上”(如在“文学编程”中)的优点。 看看这个代码片段:

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

这在function上尽可能简单。 因为这个例子是如此之短,我混淆了获取_text_has_foobar_re_search在一行中的方式。 这个代码的缺点是它在TYPO库对象的生命周期中占用一点内存; 好处是当做一个foobarsearch,你会得到两个函数调用和两个类字典查找。 有多less正则expression式被recaching,那个caching的开销在这里是不相关的。

比较这与更常见的风格,如下所示:

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

I readily admit that my style is highly unusual for python, maybe even debatable. however, in the example that more closely matches how python is mostly used, in order to do a single match, we must instantiate an object, do three instance dictionary lookups, and perform three function calls; additionally, we might get into re caching troubles when using more than 100 regexes. also, the regular expression gets hidden inside the method body, which most of the time is not such a good idea.

be it said that every subset of measures—targeted, aliased import statements; aliased methods where applicable; reduction of function calls and object dictionary lookups—can help reduce computational and conceptual complexity.

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

The votes on the accepted answer leads to the assumption that what @Triptych says is true for all cases. This is not necessarily true. One big difference is when you have to decide whether to accept a regex string or a compiled regex object as a parameter to a function:

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

It is always better to compile your regexs in case you need to reuse them.

Note the example in the timeit above simulates creation of a compiled regex object once at import time versus "on-the-fly" when required for a match.

My understanding is that those two examples are effectively equivalent. The only difference is that in the first, you can reuse the compiled regular expression elsewhere without causing it to be compiled again.

Here's a reference for you: 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.)