正则表达式比使用.replace()方法更快吗?

3
一个正则表达式是否比以下Python代码更快?
myStr.replace(","," ").replace("'"," ").replace("-","").replace("_","").replace("["," ").replace("]"," ")

这是否意味着 str 被遍历了 6 次(即 replace() 调用的次数)?使用正则表达式会更快还是相同的算法/方法论?这种情况下正则表达式长什么样子?

6
我建议你写一个并进行测试。你可能想要查看timeit - http://docs.python.org/library/timeit.html - 来完成这个任务。 - Gareth Latty
1
我已经测试了各种答案的时间,并发布了结果。 - Blair
Blair包含了我的玩笑答案(一个正则表达式解决方案),但没有包含我的认真答案(使用str.translate)。我已经纠正了这个问题。请查看我的更新答案。 - John Machin
5个回答

8

简短回答:使用 str.translate,它比标准 Python 发行版中的任何其他替代方法都更快。

长话短说:

这是否意味着 str 被迭代了 6 次(即 replace() 调用的次数)?

还可能是什么呢?

这个正则表达式会是什么样子?

您在 re 文档的哪个部分遇到了困难?

作为一个易于理解的等效方法,请尝试以下内容:

"".join(" " if c in ",'-_[]" else c for c in myStr)

或者这个:
cset = set(",'-_[]")
"".join(" " if c in cset else c for c in myStr)

就速度而言,您应该对与您期望拥有的数据类似的数据进行一些计时。不要忘记包括一个没有这些字符的测试字符串。

更新 这里是实际可用的代码:

>>> import re
>>> myStr = "a,b'c-d_f[g]h"
>>> re.sub(r"[,'\-_[\]]", " ", myStr)
'a b c d f g h'
>>>

更一般地说,为了避免你计算哪些字符需要转义,可以采取以下步骤:

>>> badchars = ",'-_[]"
>>> regex = "[" + re.escape(badchars) + "]"
>>> print regex
[\,\'\-\_\[\]]
>>> re.sub(regex, " ", myStr)
'a b c d f g h'
>>>

更新 2要用一个正则表达式完成 OP 问题似乎要求的内容:

>>> re.sub(r"[,'\-_[\]]", lambda m: "" if m.group(0) in "-_" else " ", myStr)
'a b cdf g h'

更新3为了以最快的方式做到OP问题看起来要求的内容,使用str.translate

# Python 2.X
>>> myStr = "a,b'c-d_f[g]h"
>>> chars2space = ",'[]"
>>> chars2delete = "-_"
>>> table = "".join(" " if chr(i) in chars2space else chr(i) for i in xrange(256))
>>> myStr.translate(table, chars2delete)
'a b cdf g h'

# Python 3.x
>>> myStr = "a,b'c-d_f[g]h"
>>> chars2space = ",'[]"
>>> chars2delete = "-_"
>>> table = dict((ord(c), " ") for c in chars2space)
>>> table.update(dict((ord(c), None) for c in chars2delete))
>>> myStr.translate(table)
'a b cdf g h'
>>>

更新4 我已更新Blair的计时器小工具,加入了我的解决方案(str.translate)。以下是在我的未命名Intel电脑上运行标准win32 Python 2.7.2时的结果:

# input = "a,b'c-d_f[g]h"
String replacement         : 0.00000195 seconds per replacement ( 1.00 X)
Borodin: two-regex         : 0.00000429 seconds per replacement ( 2.20 X)
John Machin: regex/lambda  : 0.00000489 seconds per replacement ( 2.51 X)
John Machin: str.translate : 0.00000042 seconds per replacement ( 0.22 X)

# input *= 100
String replacement         : 0.00001612 seconds per replacement ( 1.00 X)
Borodin: two-regex         : 0.00015821 seconds per replacement ( 9.82 X)
John Machin: regex/lambda  : 0.00036253 seconds per replacement (22.50 X)
John Machin: str.translate : 0.00000424 seconds per replacement ( 0.26 X)

# input *= 1000
String replacement         : 0.00012404 seconds per replacement ( 1.00 X)
Borodin: two-regex         : 0.00148683 seconds per replacement (11.99 X)
John Machin: regex/lambda  : 0.00360127 seconds per replacement (29.03 X)
John Machin: str.translate : 0.00003361 seconds per replacement ( 0.27 X)

# input = "nopunctuation" * 1000 i.e. same length as previous results
String replacement         : 0.00002708 seconds per replacement ( 1.00 X)
Borodin: two-regex         : 0.00018181 seconds per replacement ( 6.71 X)
John Machin: regex/lambda  : 0.00008235 seconds per replacement ( 3.04 X)
John Machin: str.translate : 0.00001780 seconds per replacement ( 0.66 X)

看起来str.translate是最先进的。


你误读了 OP。下划线和连字符应替换为空字符串,而逗号、单引号以及开放和关闭括号应该转换为空格。 - Borodin
看起来像是打错字了 :-) 更好的解决方案即将推出。敬请期待。 - John Machin
使用一个函数来替换字符串会很容易解决,但这显然会影响性能,而这正是问题的关键所在。 - Borodin

4

出于兴趣,我使用timeit编写了一个快速的计时测试,测试了其他答案中给出的方法。为了安全起见,所有正则表达式都已预编译。以下是我的净书(Ubuntu 11.10,Python 2.7.2)上的结果,从快到慢:

String replacement: 8.556e-06 seconds per replacement
Borodin's two-regex solution: 1.979e-05 seconds per replacement
John Machin's regex/lambda solution: 2.623e-05 seconds per replacement

因此,两个正则表达式的解决方案比简单的字符串替换慢2.3倍,而John Machin的单个正则表达式和lambda函数的解决方案比字符串替换慢3.06倍。

为了测试一个更长的字符串,我将原始字符串连接了100次:

String replacement: 7.600e-05 seconds per replacement
Borodin's two-regex solution: 7.894e-04 seconds per replacement
John Machin's regex/lambda solution: 1.909e-03 seconds per replacement

使用正则表达式替换字符串速度比字符串替换慢10倍,使用正则表达式/lambda表达式慢25倍。

如果将原始输入串连接1000次,则:

String replacement: 5.396e-04 seconds per replacement
Borodin's two-regex solution: 8.584e-03 seconds per replacement
John Machin's regex/lambda solution: 2.094e-02 seconds per replacement

现在,使用正则表达式的速度已经比字符串替换慢了15.9倍,而使用正则表达式/lambda的速度更慢,达到了38.8倍。

随着输入的增加,正则表达式的速度似乎越来越慢。我猜想这是因为正则表达式需要逐个字符扫描输入,而且它们执行的复杂测试比多次循环慢得多。使用lambda函数用单个正则表达式替换字符似乎比使用两个正则表达式要慢得多。

如果其他人想在自己的计算机上检查时间,请使用以下代码:

import sys
import timeit

# How many times to test each method.
n = 1000

# String to test with.
test_str = "a,b'c-d_f[g]h"

# Correct output.
correct = "a b cdf g h"

# Uncomment to try longer strings.
#test_str *= 100
#correct *= 100

# Setup code (i.e., code which shouldn't be included in the timings).
setup = """
import re

# Borodin's two-regex solution.
bre1 = re.compile(r"[,'\[\]]")
bre2 = re.compile(r"[-_]")

# John Machin's solution.
jmre = re.compile(r"[,'\-_[\]]")
repl = lambda m: "" if m.group(0) in "-_" else " "

test_str=\"{0:s}\"""".format(test_str)

# The methods.
methods = {
    'String replacement': 'result=test_str.replace(","," ").replace("\'"," ").replace("-","").replace("_","").replace("["," ").replace("]"," ")',
    'Borodin\'s two-regex solution': 'result=bre2.sub("", bre1.sub(" ", test_str))',
    'John Machin\'s regex/lambda solution': 'result=jmre.sub(repl, test_str)',
}

# Execute the setup so we can test for correctness.
exec(setup)

for method, code in methods.items():
    # Check the code gives correct results.
    exec(code)
    if(result != correct):
        sys.stdout.write('{0} gave incorrect output: {1}\n'.format(method, result))
        continue

    # Time it.
    t = timeit.timeit(code, setup, number=n)
    sys.stdout.write('{0}: {1:.3e} seconds per replacement\n'.format(method, t/n))

我已经包含了我的认真回答(str.translate),并整理了你的输出。请查看我的更新答案。 - John Machin

2
正则表达式可能会更快。一个好的正则表达式引擎(Python有一个很好的)是处理它所能处理的字符串转换的一种非常快速的方式。除非你真的很擅长于使用正则表达式,否则理解起来会比较困难。
通常情况下,在你开始编写项目时,你应该优化程序员时间(即你的时间),而不是运行时间。你花费在思考两个正则表达式调用是否比6个replace调用更快的时间,可以更好地花在编写更多代码上。
如果这个问题的源头是你完成了程序并开始运行它,发现它运行太慢了,那么现在是考虑这些事情的好时机,尽管你可能需要进行一堆性能测量,才能找出运行时花费了哪些时间。你的程序中大部分运行时间很可能花费在相对较小的代码段中,因此一旦你能够识别它们,就可以集中精力使它们更快。我的直觉是这些replace调用不是这样的位置。但是,即使是经验丰富的程序员在编写代码时也很难预测他们代码中的"hotspots"。
另一方面,如果这个问题是由于你刚刚编写了replace调用,并在想着也许可以以更快的方式运行它,那么你应该继续编写更多代码。毕竟,那是编程的有趣之处。 :)

1

我相信,在这种情况下,正则表达式会更快,因为使用 replace 每次都必须迭代整个字符串。


1
我认为正则表达式几乎肯定会更快,但你应该进行简单的基准测试以确保。您当前的代码执行以下单独步骤:
myStr.replace(","," ")
myStr.replace("'"," ")
myStr.replace("-","")
myStr.replace("_","")
myStr.replace("["," ")
myStr.replace("]"," ")

使用正则表达式可以分两步完成:

re.sub(r"[,'\[\]]", " ", myStr)
re.sub(r"[-_]", "", myStr)

1
-1 (a) 它等同于 x = myStr.replace(","," "); x = x.replace("'"," "); 等等 (b) 分号应该改为右括号 (c) 第一行应该以 myStr = 开始 (d) 你可以用一个正则表达式完成它。 - John Machin
感谢您的更正。请演示如何在一个正则表达式中完成这个操作? - Borodin
re.sub(r"-_]", "", myStr) 缺少左方括号 - 应该是 re.sub(r"[-_]", "", myStr)。另外,我进行了一些基准测试(请参见我的答案),似乎字符串替换仍然更快。 - Blair

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接