我以犯罪效率低下的方式使用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'', item, 1) item = MatchedDoubleQuotes.sub(r'', 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
解决方案
切勿创建自己的编程语言。曾经。 (我曾经对此规则有例外,但现在不再例外。)
我们总可以使用一种现有的语言来更好地满足需求。如果我们详细说明了用例,那么人们可能会选择合适的语言。
我们可以使用r"(\" |')(。*?)\ 1"
一次性匹配两种引号,而\ 1指的是第一组,因此它只会匹配匹配的引号。
不要连续两次调用搜索(在条件循环中和循环中的第一条语句中)。在循环之前调用一次(并缓存结果),然后在循环的最终语句中调用。
我们在调用re.compile相当多。这些的全局变量在这里不会受到影响。
可能会改进的第一件事是将重新编译移到函数之外。编译被缓存,但是检查此编译是否被编译会产生一定的速度。
另一种可能性是使用单个正则表达式,如下所示:
MatchedQuotes = re.compile(r"(['\"])(.*)", re.LOCALE) item = MatchedQuotes.sub(r'', item, 1)
最后,我们可以将其合并到processVariables中的正则表达式中。采纳Torsten Marek的建议为re.sub使用函数,这可以极大地改善和简化事情。
VariableDefinition = re.compile(r'<%(["\']?)(.*?)=(["\']?)(.*?)%>', re.LOCALE) VarRepl = re.compile(r'<%(["\']?)(.*?)%>', 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
[编辑]添加缺少的非贪婪说明符
[Edit2]添加了.upper()调用,因此像原始版本一样不区分大小写
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的建议,并保留编译后的正则表达式。
相同的配方可以应用于第一个循环,仅在该处需要首先存储变量的值,并始终返回"""作为替换。
如果一个正则表达式仅包含一个。*通配符和文字,则可以使用find和rfind来查找开始和结束定界符。
如果仅包含一系列。*?通配符和文字,那么我们可以使用一系列查找来完成工作。
如果代码是时间紧迫的,那么完全不使用regexp的此开关可能会提高速度。
另外,在我看来,这是一种LL可解析的语言。我们可能会寻找一个可以为我们解析此类内容的库。我们还可以使用递归调用进行一次解析-例如,我们可以实现processVariables函数以仅消耗第一个报价,然后调用报价匹配函数以消耗下一个报价,依此类推。
创建模板语言固然很好,但是模板语言的目标之一不是易读性和高效解析能力吗?我们提供的示例似乎都不是。
正如杰米·扎温斯基(Jamie Zawinsky)所说:
Some people, when confronted with a problem, think "I know, I'll use regular expressions!" Now they have two problems.
如果正则表达式是我们创建的问题的解决方案,那么最好的选择不是编写更好的正则表达式,而是重新设计方法以完全消除对它们的使用。正则表达式很复杂,昂贵,很难维护,并且(理想情况下)只能用于解决其他人创建的问题。
为什么不使用XML和XSLT而不是创建自己的模板语言?在XSLT中,我们要执行的操作非常简单。
为什么不使用Mako?严重地。我们需要Mako不具备的功能?也许我们可以适应或者扩展已经有效的功能。