Python中的字符串连接与字符串替换

时间:2008-12-17 23:39:40

标签: python string string-concatenation

在Python中,使用字符串连接和字符串替换的位置和时间都不包括在内。由于字符串连接在性能上有很大的提升,这是一种风格决定而不是实际的决定吗?

对于一个具体的例子,如何处理灵活URI的构造:

DOMAIN = 'http://stackoverflow.com'
QUESTIONS = '/questions'

def so_question_uri_sub(q_num):
    return "%s%s/%d" % (DOMAIN, QUESTIONS, q_num)

def so_question_uri_cat(q_num):
    return DOMAIN + QUESTIONS + '/' + str(q_num)

编辑:还有关于加入字符串列表和使用命名替换的建议。这些是中心主题的变体,哪种方式是正确的方式来做到这一点?感谢您的回复!

9 个答案:

答案 0 :(得分:55)

根据我的机器,连接速度(显着)更快。但从风格上来说,如果表现并不重要,我愿意支付替代价格。好吧,如果我需要格式化,甚至不需要问这个问题......除了使用插值/模板之外别无选择。

>>> import timeit
>>> def so_q_sub(n):
...  return "%s%s/%d" % (DOMAIN, QUESTIONS, n)
...
>>> so_q_sub(1000)
'http://stackoverflow.com/questions/1000'
>>> def so_q_cat(n):
...  return DOMAIN + QUESTIONS + '/' + str(n)
...
>>> so_q_cat(1000)
'http://stackoverflow.com/questions/1000'
>>> t1 = timeit.Timer('so_q_sub(1000)','from __main__ import so_q_sub')
>>> t2 = timeit.Timer('so_q_cat(1000)','from __main__ import so_q_cat')
>>> t1.timeit(number=10000000)
12.166618871951641
>>> t2.timeit(number=10000000)
5.7813972166853773
>>> t1.timeit(number=1)
1.103492206766532e-05
>>> t2.timeit(number=1)
8.5206360154188587e-06

>>> def so_q_tmp(n):
...  return "{d}{q}/{n}".format(d=DOMAIN,q=QUESTIONS,n=n)
...
>>> so_q_tmp(1000)
'http://stackoverflow.com/questions/1000'
>>> t3= timeit.Timer('so_q_tmp(1000)','from __main__ import so_q_tmp')
>>> t3.timeit(number=10000000)
14.564135316080637

>>> def so_q_join(n):
...  return ''.join([DOMAIN,QUESTIONS,'/',str(n)])
...
>>> so_q_join(1000)
'http://stackoverflow.com/questions/1000'
>>> t4= timeit.Timer('so_q_join(1000)','from __main__ import so_q_join')
>>> t4.timeit(number=10000000)
9.4431309007150048

答案 1 :(得分:23)

不要忘记命名替换:

def so_question_uri_namedsub(q_num):
    return "%(domain)s%(questions)s/%(q_num)d" % locals()

答案 2 :(得分:12)

警惕在循环中连接字符串!字符串连接的开销与结果的长度成正比。循环使您直接进入N平方的土地。有些语言会优化连接到最近分配的字符串,但依靠编译器将二次算法优化为线性是有风险的。最好使用带有整个字符串列表的原语(join?),进行单个分配,并将它们连接在一起。

答案 3 :(得分:11)

“由于字符串连接在性能上有很大提升......”

如果表现很重要,这很有用。

然而,我见过的性能问题从来没有归结为字符串操作。我通常遇到I / O问题,排序和O( n 2 )操作是瓶颈。

在字符串操作是性能限制器之前,我会坚持使用明显的东西。大多数情况下,当它是一行或更少时,它是替换,当它是有意义的时候是串联,当它很大时,它是一个模板工具(如Mako)。

答案 4 :(得分:10)

您想要连接/插入的内容以及您希望如何格式化结果应该会推动您的决定。

  • 字符串插值允许您轻松添加格式。实际上,您的字符串插值版本与您的串联版本不同;它实际上在q_num参数之前添加了一个额外的正斜杠。要做同样的事情,你必须在那个例子中写return DOMAIN + QUESTIONS + "/" + str(q_num)

  • 插值可以更容易地格式化数字;串联形式下"%d of %d (%2.2f%%)" % (current, total, total/current)的可读性要低得多。

  • 当您没有固定数量的项目进行字符串化时,连接很有用。

另外,要知道Python 2.6引入了一个新版本的字符串插值,称为string templating

def so_question_uri_template(q_num):
    return "{domain}/{questions}/{num}".format(domain=DOMAIN,
                                               questions=QUESTIONS,
                                               num=q_num)

字符串模板最终将取代%-interpolation,但我认为这种情况不会发生很长一段时间。

答案 5 :(得分:7)

我只是出于好奇而测试不同字符串连接/替换方法的速度。关于这个主题的谷歌搜索把我带到了这里。我以为我会发布我的测试结果,希望它可以帮助某人做出决定。

    import timeit
    def percent_():
            return "test %s, with number %s" % (1,2)

    def format_():
            return "test {}, with number {}".format(1,2)

    def format2_():
            return "test {1}, with number {0}".format(2,1)

    def concat_():
            return "test " + str(1) + ", with number " + str(2)

    def dotimers(func_list):
            # runs a single test for all functions in the list
            for func in func_list:
                    tmr = timeit.Timer(func)
                    res = tmr.timeit()
                    print "test " + func.func_name + ": " + str(res)

    def runtests(func_list, runs=5):
            # runs multiple tests for all functions in the list
            for i in range(runs):
                    print "----------- TEST #" + str(i + 1)
                    dotimers(func_list)

...运行runtests((percent_, format_, format2_, concat_), runs=5)后,我发现%方法在这些小字符串上的速度大约是其他方法的两倍。 concat方法总是最慢的(几乎没有)。在format()方法中切换位置时存在非常微小的差异,但切换位置始终至少比常规格式方法慢.01。

测试结果样本:

    test concat_()  : 0.62  (0.61 to 0.63)
    test format_()  : 0.56  (consistently 0.56)
    test format2_() : 0.58  (0.57 to 0.59)
    test percent_() : 0.34  (0.33 to 0.35)

我运行这些因为我在脚本中使用字符串连接,我想知道成本是多少。我以不同的顺序运行它们以确保没有任何干扰,或者首先或最后获得更好的性能。在旁注中,我将一些较长的字符串生成器输入到"%s" + ("a" * 1024)等函数中,而常规concat几乎是使用format%方法的3倍(1.1 vs 2.8) 。我想这取决于字符串,以及你想要实现的目标。如果性能真的很重要,那么尝试不同的东西并测试它们会更好。我倾向于选择速度可读性,除非速度成为一个问题,但那只是我。所以不喜欢我的复制/粘贴,我不得不在所有东西上放置8个空格以使其看起来正确。我通常使用4。

答案 6 :(得分:4)

请记住,风格决定实际决策,如果您计划维护或调试代码:-) Knuth有一句名言(可能引用Hoare?):“我们应该忘记小效率,大约97%的时间说:过早的优化是所有邪恶的根源。“

只要你小心不要(比方说)将O(n)任务变成O(n 2 )任务,我会选择你最容易理解的任何一个。

答案 7 :(得分:0)

我尽可能使用替代品。如果我在for-loop中构建一个字符串,我只使用连接。

答案 8 :(得分:-1)

实际上,在这种情况下(构建路径),正确的做法是使用os.path.join。不是字符串连接或插值