我以犯罪效率低下的方式使用Python正则表达式

时间:2020-03-06 14:51:05  来源:igfitidea点击:

我的目标是创建一种非常简单的模板语言。目前,我正在用一个值替换变量,如下所示:

此输入:

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不具备的功能?也许我们可以适应或者扩展已经有效的功能。