我在使用Python正则表达式时效率极低

8 投票
10 回答
2226 浏览
提问于 2025-04-11 09:20

我的目标是创建一个非常简单的模板语言。目前,我正在处理一个把变量替换成值的功能,像这样:

输入内容:

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 个回答

2

创建一个模板语言听起来不错,但这个模板语言的目标之一应该是让人容易读懂和高效解析吧?你给的例子似乎都不符合这两点。

正如Jamie Zawinsky所说:

有些人在遇到问题时,会想:“我知道,我来用正则表达式!”结果他们就多了一个问题。

如果正则表达式是你自己创造的问题的解决方案,那么最好的办法不是写一个更好的正则表达式,而是重新设计你的方法,完全避免使用它们。正则表达式很复杂,使用起来成本高,维护起来也非常困难,理想情况下,它们应该只在解决别人创造的问题时才使用。

4

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 的建议,建议你保留编译好的正则表达式。

同样的方法也可以用在第一个循环中,只不过在那里你需要先保存变量的值,并且总是返回 "" 作为替换内容。

12

首先,可以尝试把 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() 调用,使其像原版一样不区分大小写

撰写回答