简短回答
如果您直接调用compiled_pattern.search(text)
,它不会调用_compile
,因此它比re.search(pattern, text)
快得多,并且比re.search(compiled_pattern, text)
更快。
这种性能差异是由于缓存中的KeyError
和编译模式的慢哈希计算引起的。
re
函数和SRE_Pattern
方法
每当调用具有pattern
作为第一个参数的re
函数(例如re.search(pattern, string)
或re.findall(pattern, string)
)时,Python 首先尝试使用_compile
编译pattern
,然后调用编译模式对应的方法。例如:代码:
def search(pattern, string, flags=0):
"""Scan through string looking for a match to the pattern, returning
a match object, or None if no match was found."""
return _compile(pattern, flags).search(string)
请注意,pattern
可以是字符串或已编译的模式(SRE_Pattern
实例)。
_compile
这是 _compile
的简化版本。我只是删掉了调试和标志检查:
_cache = {}
_pattern_type = type(sre_compile.compile("", 0))
_MAXCACHE = 512
def _compile(pattern, flags):
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):
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 len(_cache) >= _MAXCACHE:
_cache.clear()
loc = None
_cache[type(pattern), pattern, flags] = p, loc
return p
_compile
和字符串模式
当使用字符串模式调用_compile
时,编译后的模式将保存在_cache
字典中。下次调用相同函数时(例如在许多timeit
运行期间),_compile
简单地检查_cache
是否已经看到过此字符串,并返回相应的编译模式。
使用Spyder中的ipdb
调试器,在执行过程中轻松深入了解re.py
。
import re
pattern = 'sed'
text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod' \
'tempor incididunt ut labore et dolore magna aliqua.'
compiled_pattern = re.compile(pattern)
re.search(pattern, text)
re.search(pattern, text)
在第二个re.search(pattern, text)
处设置断点后,可以看到这对:
{(<class 'str'>, 'sed', 0): (re.compile('sed'), None)}
存储在_cache
中。编译后的模式将直接返回。
使用已编译的模式进行_compile
慢速哈希
如果已经编译了一个模式,调用_compile
会发生什么?
首先,_compile
会检查模式是否在_cache
中。为此,它需要计算其哈希值。对于编译后的模式,这种计算比字符串要慢得多:
In [1]: import re
In [2]: pattern = "(?:a(?:b(?:b\\é|sorbed)|ccessing|gar|l(?:armists|ternation)|ngels|pparelled|u(?:daciousness's|gust|t(?:horitarianism's|obiographi
...: es)))|b(?:aden|e(?:nevolently|velled)|lackheads|ooze(?:'s|s))|c(?:a(?:esura|sts)|entenarians|h(?:eeriness's|lorination)|laudius|o(?:n(?:form
...: ist|vertor)|uriers)|reeks)|d(?:aze's|er(?:elicts|matologists)|i(?:nette|s(?:ciplinary|dain's))|u(?:chess's|shanbe))|e(?:lectrifying|x(?:ampl
...: ing|perts))|farmhands|g(?:r(?:eased|over)|uyed)|h(?:eft|oneycomb|u(?:g's|skies))|i(?:mperturbably|nterpreting)|j(?:a(?:guars|nitors)|odhpurs
...: 's)|kindnesses|m(?:itterrand's|onopoly's|umbled)|n(?:aivet\\é's|udity's)|p(?:a(?:n(?:els|icky|tomimed)|tios)|erpetuating|ointer|resentation|
...: yrite)|r(?:agtime|e(?:gret|stless))|s(?:aturated|c(?:apulae|urvy's|ylla's)|inne(?:rs|d)|m(?:irch's|udge's)|o(?:lecism's|utheast)|p(?:inals|o
...: onerism's)|tevedore|ung|weetest)|t(?:ailpipe's|easpoon|h(?:ermionic|ighbone)|i(?:biae|entsin)|osca's)|u(?:n(?:accented|earned)|pstaging)|v(?
...: :alerie's|onda)|w(?:hirl|ildfowl's|olfram)|zimmerman's)"
In [3]: compiled_pattern = re.compile(pattern)
In [4]: % timeit hash(pattern)
126 ns ± 0.358 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [5]: % timeit hash(compiled_pattern)
7.67 µs ± 21 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
hash(compiled_pattern)
比hash(pattern)
慢60倍。
KeyError
当一个未知的pattern
出现时,_cache[type(pattern), pattern, flags]
会出现KeyError
错误。
KeyError
被处理并忽略。然后,_compile
才检查该模式是否已经编译。如果已经编译,则返回该模式,而不写入缓存。
这意味着下一次使用相同的已编译模式调用_compile
时,它将再次计算无用的、缓慢的哈希值,但仍然会出现KeyError
错误。
错误处理是昂贵的,我想这可能是为什么re.search(compiled_pattern, text)
比re.search(pattern, text)
慢的主要原因。
这种奇怪的行为可能是为了加速字符串模式的调用,但如果_compile
使用已经编译过的模式,可能写一个警告是个好主意。
compiled_pattern.search(text)
而不是re.search(compiled_pattern, text)
。 - asongtoruincompiled_pattern.search(text)
会快3倍。不过,很奇怪的是,re.search(compiled_pattern, text)
似乎不能做到同样的效果。 - tobias_kre.search
只是简单地执行return _compile(pattern, flags).search(string)
。没有检查模式是否已经编译。 - asongtoruinre.py:294
。(Py3.6的行号)。 - polwel