在Python中,str.replace(..).replace(..)重复使用是标准习惯吗?

34 投票
9 回答
19139 浏览
提问于 2025-04-15 20:38

比如说,我想要一个函数来处理字符串,以便在HTML中使用(就像Django里的escape过滤器一样):

    def escape(string):
        """
        Returns the given string with ampersands, quotes and angle 
        brackets encoded.
        """
        return string.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace("'", '&#39;').replace('"', '&quot;')

这个方法可以用,但很快就会变得复杂,而且效率不高(在这个例子中,字符串被重复遍历了5次)。更好的方法可能是这样的:

    def escape(string):
        """
        Returns the given string with ampersands, quotes and angle 
        brackets encoded.
        """
        # Note that ampersands must be escaped first; the rest can be escaped in 
        # any order.
        return replace_multi(string.replace('&', '&amp;'),
                             {'<': '&lt;', '>': '&gt;', 
                              "'": '&#39;', '"': '&quot;'})

有没有这样的函数,或者在Python中,大家通常还是用我之前写的那种方式呢?

9 个回答

13

我更喜欢像这样的干净代码:

substitutions = [
    ('<', '&lt;'),
    ('>', '&gt;'),
    ...]

for search, replacement in substitutions:
    string = string.replace(search, replacement)
20

我们可以试试不同的方法来做这件事,看看哪种方法最快(假设我们只关心最快的方式)。

def escape1(input):
        return input.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace("'", '&#39;').replace('"', '&quot;')

translation_table = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    "'": '&#39;',
    '"': '&quot;',
}

def escape2(input):
        return ''.join(translation_table.get(char, char) for char in input)

import re
_escape3_re = re.compile(r'[&<>\'"]')
def _escape3_repl(x):
    s = x.group(0)
    return translation_table.get(s, s)
def escape3(x):
    return _escape3_re.sub(_escape3_repl, x)

def escape4(x):
    return unicode(x).translate(translation_table)

test_strings = (
    'Nothing in there.',
    '<this is="not" a="tag" />',
    'Something & Something else',
    'This one is pretty long. ' * 50
)

import time

for test_i, test_string in enumerate(test_strings):
    print repr(test_string)
    for func in escape1, escape2, escape3, escape4:
        start_time = time.time()
        for i in xrange(1000):
            x = func(test_string)
        print '\t%s done in %.3fms' % (func.__name__, (time.time() - start_time))
    print

运行这个代码会得到:

'Nothing in there.'
    escape1 done in 0.002ms
    escape2 done in 0.009ms
    escape3 done in 0.001ms
    escape4 done in 0.005ms

'<this is="not" a="tag" />'
    escape1 done in 0.002ms
    escape2 done in 0.012ms
    escape3 done in 0.009ms
    escape4 done in 0.007ms

'Something & Something else'
    escape1 done in 0.002ms
    escape2 done in 0.012ms
    escape3 done in 0.003ms
    escape4 done in 0.007ms

'This one is pretty long. <snip>'
    escape1 done in 0.008ms
    escape2 done in 0.386ms
    escape3 done in 0.011ms
    escape4 done in 0.310ms

看起来一个一个地替换它们是最快的。

编辑:再次运行测试,进行了1000000次迭代,前面三个字符串的结果如下(第四个在我这台机器上运行太久,我等不及了 =P):

'Nothing in there.'
    escape1 done in 0.001ms
    escape2 done in 0.008ms
    escape3 done in 0.002ms
    escape4 done in 0.005ms

'<this is="not" a="tag" />'
    escape1 done in 0.002ms
    escape2 done in 0.011ms
    escape3 done in 0.009ms
    escape4 done in 0.007ms

'Something & Something else'
    escape1 done in 0.002ms
    escape2 done in 0.011ms
    escape3 done in 0.003ms
    escape4 done in 0.007ms

这些数字基本上是差不多的。在第一种情况下,它们实际上更一致,因为直接替换字符串现在是最快的。

24

你有没有遇到过应用程序运行得太慢的情况?你分析了一下,发现像下面这段代码让它变得慢?瓶颈往往出现在意想不到的地方。

这段代码目前要遍历字符串五次,每次做一件事。你建议只遍历一次,可能每次做五件事(或者至少每次做点什么)。我不太确定这样做是否一定会更好。现在使用的算法复杂度是 O(n*m)(假设字符串的长度比规则里的内容要长),其中 n 是字符串的长度,m 是替换规则的数量。我觉得你可以把算法复杂度降低到 O(n*log(m)),在我们讨论的特定情况下——原始内容都是单个字符(但在一般情况下多次调用 replace 就不一定了)——可以降到 O(n),但这并不重要,因为 m 是 5n 是无限的

如果 m 是固定的,那么两种方案的复杂度都变成 O(n)。我不太确定把五次简单的遍历变成一次复杂的遍历是否值得,实际上我现在也无法猜测这样做的时间。如果有某种原因让它能更好地扩展,那我会觉得这更值得去做。

在一次遍历中完成所有操作,而不是连续遍历,还需要回答一些关于如何处理冲突规则以及如何应用这些规则的问题。使用一连串的 replace 可以清楚地解决这些问题。

撰写回答