我在使用Python正则表达式时效率极低
我的目标是创建一个非常简单的模板语言。目前,我正在处理一个把变量替换成值的功能,像这样:
输入内容:
The Web
应该输出这个:
The Web This Is A Test Variable
我已经让它工作了。但是看着我的代码,我发现对同样的字符串运行了多个相同的正则表达式,这让我觉得效率不高。肯定有更好、更符合Python风格的方法。(尤其是那两个“while”循环,真的让我觉得不爽。)
这段代码通过了单元测试,所以如果这只是过早的优化,请告诉我——我愿意放弃这个想法。文档中可能会有几十个这样的变量定义和使用,但不会有成百上千个。不过我怀疑,其他人可能会有明显的改进方法,我很好奇StackOverflow的朋友们会提出什么建议。
def stripMatchedQuotes(item):
MatchedSingleQuotes = re.compile(r"'(.*)'", re.LOCALE)
MatchedDoubleQuotes = re.compile(r'"(.*)"', re.LOCALE)
item = MatchedSingleQuotes.sub(r'\1', item, 1)
item = MatchedDoubleQuotes.sub(r'\1', item, 1)
return item
def processVariables(item):
VariableDefinition = re.compile(r'<%(.*?)=(.*?)%>', re.LOCALE)
VariableUse = re.compile(r'<%(.*?)%>', re.LOCALE)
Variables={}
while VariableDefinition.search(item):
VarName, VarDef = VariableDefinition.search(item).groups()
VarName = stripMatchedQuotes(VarName).upper().strip()
VarDef = stripMatchedQuotes(VarDef.strip())
Variables[VarName] = VarDef
item = VariableDefinition.sub('', item, 1)
while VariableUse.search(item):
VarName = stripMatchedQuotes(VariableUse.search(item).group(1).upper()).strip()
item = VariableUse.sub(Variables[VarName], item, 1)
return item
10 个回答
创建一个模板语言听起来不错,但这个模板语言的目标之一应该是让人容易读懂和高效解析吧?你给的例子似乎都不符合这两点。
正如Jamie Zawinsky所说:
有些人在遇到问题时,会想:“我知道,我来用正则表达式!”结果他们就多了一个问题。
如果正则表达式是你自己创造的问题的解决方案,那么最好的办法不是写一个更好的正则表达式,而是重新设计你的方法,完全避免使用它们。正则表达式很复杂,使用起来成本高,维护起来也非常困难,理想情况下,它们应该只在解决别人创造的问题时才使用。
sub
这个函数可以接收一个可调用的对象作为参数,而不仅仅是一个简单的字符串。这样,你就可以通过一个函数调用来替换所有的变量:
>>> import re
>>> var_matcher = re.compile(r'<%(.*?)%>', re.LOCALE)
>>> string = '<%"TITLE"%> <%"SHMITLE"%>'
>>> values = {'"TITLE"': "I am a title.", '"SHMITLE"': "And I am a shmitle."}
>>> var_matcher.sub(lambda m: vars[m.group(1)], string)
'I am a title. And I am a shmitle.
按照 eduffy.myopenid.com 的建议,建议你保留编译好的正则表达式。
同样的方法也可以用在第一个循环中,只不过在那里你需要先保存变量的值,并且总是返回 ""
作为替换内容。
首先,可以尝试把 re.compile 移到函数外面。这样做的好处是编译结果会被缓存,但如果每次都要检查它是否已经编译过,会影响速度。
另外一种方法是使用一个单一的正则表达式,如下所示:
MatchedQuotes = re.compile(r"(['\"])(.*)\1", re.LOCALE)
item = MatchedQuotes.sub(r'\2', item, 1)
最后,你可以把这些合并到 processVariables 里的正则表达式中。参考 Torsten Marek 的建议,使用一个函数来处理 re.sub,这样可以大大提高效率并简化代码。
VariableDefinition = re.compile(r'<%(["\']?)(.*?)\1=(["\']?)(.*?)\3%>', re.LOCALE)
VarRepl = re.compile(r'<%(["\']?)(.*?)\1%>', re.LOCALE)
def processVariables(item):
vars = {}
def findVars(m):
vars[m.group(2).upper()] = m.group(4)
return ""
item = VariableDefinition.sub(findVars, item)
return VarRepl.sub(lambda m: vars[m.group(2).upper()], item)
print processVariables('<%"TITLE"="This Is A Test Variable"%>The Web <%"TITLE"%>')
以下是我进行 100000 次运行的时间记录:
Original : 13.637
Global regexes : 12.771
Single regex : 9.095
Final version : 1.846
[编辑] 添加了缺失的非贪婪修饰符
[编辑2] 添加了 .upper() 调用,使其像原版一样不区分大小写