python中最有效的字符串连接方法是什么?


148

有没有在Python任何有效的质量字符串连接方法(如StringBuilder的 C#或StringBuffer的在Java中)?我在这里找到以下方法:

  • 简单串联使用 +
  • 使用字符串列表和join方法
  • UserStringMutableString模块使用
  • 使用字符数组和array模块
  • cStringIOStringIO模块使用

但是您的专家使用或建议了什么,为什么?

[ 这里的一个相关问题 ]



为了将已知片段连接成一个片段,Python 3.6的f''格式字符串将比以前的Python版本中的任何替代版本都要快。
Antti Haapala'7

Answers:


127

您可能对此感兴趣:Guido 的优化轶事。尽管还应该记住这是一篇老文章,并且早于诸如此类的内容的存在''.join(尽管我猜string.joinfields大致相同)

鉴于此,如果您可以将问题塞入该array模块,则该模块可能是最快的。但是''.join可能足够快,并且具有惯用的好处,因此其他Python程序员更容易理解。

最后,优化的黄金法则:除非您知道自己需要进行优化,否则不要进行优化,而要进行衡量而不是猜测。

您可以使用该timeit模块测量不同的方法。这样可以告诉您哪个最快,而不是互联网上的随机陌生人进行猜测。


1
想要添加关于何时进行优化的要点:确保针对最坏的情况进行测试。例如,我可以增加示例,以使当前代码从运行0.17秒变为运行170秒。好吧,我想在较大的样本量下进行测试,因为那里的差异较小。
鳍2015年

2
“除非您知道需要,否则请不要进行优化。” 除非您只是使用名义上不同的习惯用法,否则您可以毫不费力地避免重新编写代码。
jeremyjjbrown

1
您需要了解的一个地方是面试(这始终是增进深刻理解的好时机)。不幸的是,我还没有找到任何有关此的现代文章。(1)Java / C#字符串在2017年仍然那么糟糕吗?(2)C ++呢?(3)现在介绍Python最新和最重要的内容,重点讨论需要进行数百万个串联的情况。我们可以相信联接将在线性时间内起作用吗?
user1854182

“足够快”意味着.join()什么?主要问题是,是否a)创建用于连接的字符串的副本(类似于s = s + 'abc'),这需要O(n)运行时,或者b)简单地追加到现有字符串,而不创建需要O(1)的副本? ?
CGFoX

64

''.join(sequenceofstrings) 通常是最有效的方法-最简单,最快。


3
@mshsayem,在Python中,序列可以是任何可枚举的对象,甚至是函数。
Nick Dandoulakis 09年

2
我绝对喜欢这个''.join(sequence)成语。产生逗号分隔的列表特别有用:', '.join([1, 2, 3])给出字符串'1, 2, 3'
安德鲁·基顿

7
@mshsayem:"".join(chr(x) for x in xrange(65,91))---在这种情况下,join的参数是一个迭代器,通过生成器表达式创建。没有构造的临时列表。
balpha

2
@balpha:但生成器版本比列表理解版本慢:C:\ temp> python -mtimeit“''.join(chr(x)for xrange xrange(65,91))” 100000循环,最好3:每个循环9.71个usec C:\ temp> python -mtimeit“''.join([xrange(65,91)中x的chr(x)])” 100000个循环,最佳3:3:每个循环7.1个usec
hughdbrown

1
@hughdbrown,是的,当您有空闲内存时,wazoo(典型的timeit情况)listcomp可以比genexp更好地优化,通常可以提高20-30%。当记忆的紧绷情况有所不同时-很难及时重现!-)
亚历克斯·马丁里

58

Python 3.6改变了使用文字字符串插值对已知组件进行字符串连接的游戏。

根据mkoistinen的答案给出测试用例,有字符串

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'

竞争者是

  • f'http://{domain}/{lang}/{path}'- 0.151微秒

  • 'http://%s/%s/%s' % (domain, lang, path) -0.321微秒

  • 'http://' + domain + '/' + lang + '/' + path -0.356微秒

  • ''.join(('http://', domain, '/', lang, '/', path))- 0.249微秒(请注意,构建一个定长元组比构建一个定长列表更快)。

因此,目前最短和最漂亮的代码也是最快的。

在Python 3.6的Alpha版本中,f''字符串的实现是最慢的-实际上,生成的字节代码几乎等同于''.join()带有不必要调用的情况,str.__format__而没有参数的调用则只会返回self不变。这些效率低下问题已在3.6决赛之前解决。

速度可以与Python 2最快的方法(+在我的计算机上串联)形成对比。而这需要0.203 μs的8位字符串,0.259微秒如果字符串所有的Unicode。


38

这取决于您在做什么。

在Python 2.5之后,使用+运算符进行字符串连接非常快。如果您只是串联几个值,则使用+运算符最有效:

>>> x = timeit.Timer(stmt="'a' + 'b'")
>>> x.timeit()
0.039999961853027344

>>> x = timeit.Timer(stmt="''.join(['a', 'b'])")
>>> x.timeit()
0.76200008392333984

但是,如果将一个字符串放入一个循环中,则最好使用列表连接方法:

>>> join_stmt = """
... joined_str = ''
... for i in xrange(100000):
...   joined_str += str(i)
... """
>>> x = timeit.Timer(join_stmt)
>>> x.timeit(100)
13.278000116348267

>>> list_stmt = """
... str_list = []
... for i in xrange(100000):
...   str_list.append(str(i))
... ''.join(str_list)
... """
>>> x = timeit.Timer(list_stmt)
>>> x.timeit(100)
12.401000022888184

...但是请注意,在差异变得明显之前,您必须将相对大量的字符串放在一起。


2
1)在您的第一次测量中,可能是花费时间的列表构造。尝试使用元组。2)CPython总体上表现良好,但是其他Python实现在+和+ =方面表现较差
u0b34a0f6ae 2009年

22

按照约翰·福伊(John Fouhy)的回答,除非必须这样做,否则不要进行优化,但是,如果您在这里问这个问题,可能正是因为您必须这样做。就我而言,我需要从字符串变量中组合一些URL……要快。我注意到(到目前为止)似乎没有人在考虑使用字符串格式方法,所以我认为我会尝试这样做,并且主要出于温和的兴趣,我认为我会把字符串插值运算符扔在那里,以获得更好的度量。老实说,我不认为这两个都会叠加成直接的'+'操作或''.join()。但猜猜怎么了?在我的Python 2.7.5系统上,字符串插值运算符将它们全部规则化,而string.format()的性能最差:

# concatenate_test.py

from __future__ import print_function
import timeit

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'
iterations = 1000000

def meth_plus():
    '''Using + operator'''
    return 'http://' + domain + '/' + lang + '/' + path

def meth_join():
    '''Using ''.join()'''
    return ''.join(['http://', domain, '/', lang, '/', path])

def meth_form():
    '''Using string.format'''
    return 'http://{0}/{1}/{2}'.format(domain, lang, path)

def meth_intp():
    '''Using string interpolation'''
    return 'http://%s/%s/%s' % (domain, lang, path)

plus = timeit.Timer(stmt="meth_plus()", setup="from __main__ import meth_plus")
join = timeit.Timer(stmt="meth_join()", setup="from __main__ import meth_join")
form = timeit.Timer(stmt="meth_form()", setup="from __main__ import meth_form")
intp = timeit.Timer(stmt="meth_intp()", setup="from __main__ import meth_intp")

plus.val = plus.timeit(iterations)
join.val = join.timeit(iterations)
form.val = form.timeit(iterations)
intp.val = intp.timeit(iterations)

min_val = min([plus.val, join.val, form.val, intp.val])

print('plus %0.12f (%0.2f%% as fast)' % (plus.val, (100 * min_val / plus.val), ))
print('join %0.12f (%0.2f%% as fast)' % (join.val, (100 * min_val / join.val), ))
print('form %0.12f (%0.2f%% as fast)' % (form.val, (100 * min_val / form.val), ))
print('intp %0.12f (%0.2f%% as fast)' % (intp.val, (100 * min_val / intp.val), ))

结果:

# python2.7 concatenate_test.py
plus 0.360787868500 (90.81% as fast)
join 0.452811956406 (72.36% as fast)
form 0.502608060837 (65.19% as fast)
intp 0.327636957169 (100.00% as fast)

如果我使用较短的域和较短的路径,则插值仍然胜出。但是,更长的字符串之间的区别更加明显。

现在,我有了一个不错的测试脚本,我也在Python 2.6、3.3和3.4下进行了测试,这是结果。在Python 2.6中,加号运算符是最快的!在Python 3上,join胜出。注意:这些测试在我的系统上是非常可重复的。因此,“ plus”在2.6上总是更快,“ intp”在2.7上总是更快,而“ join”在Python 3.x上总是更快。

# python2.6 concatenate_test.py
plus 0.338213920593 (100.00% as fast)
join 0.427221059799 (79.17% as fast)
form 0.515371084213 (65.63% as fast)
intp 0.378169059753 (89.43% as fast)

# python3.3 concatenate_test.py
plus 0.409130576998 (89.20% as fast)
join 0.364938726001 (100.00% as fast)
form 0.621366866995 (58.73% as fast)
intp 0.419064424001 (87.08% as fast)

# python3.4 concatenate_test.py
plus 0.481188605998 (85.14% as fast)
join 0.409673971997 (100.00% as fast)
form 0.652010936996 (62.83% as fast)
intp 0.460400978001 (88.98% as fast)

# python3.5 concatenate_test.py
plus 0.417167026084 (93.47% as fast)
join 0.389929617057 (100.00% as fast)
form 0.595661019906 (65.46% as fast)
intp 0.404455224983 (96.41% as fast)

学过的知识:

  • 有时,我的假设是完全错误的。
  • 针对系统环境进行测试。您将在生产中运行。
  • 字符串插值还没有结束!

tl; dr:

  • 如果使用2.6,请使用+运算符。
  • 如果您使用的是2.7,请使用'%'运算符。
  • 如果您使用的是3.x,请使用''.join()。

2
注意:对于3.6+,文字字符串插值仍然更快:f'http://{domain}/{lang}/{path}'
TemporalWolf

1
此外,.format()有三种形式,为了从快到慢:"{}".format(x)"{0}".format(x)"{x}".format(x=x)
TemporalWolf

真正的教训:当您的问题范围较小时(例如,编写短字符串),大多数情况下方法并不重要。而且即使很重要,例如您确实在构建一百万个字符串,开销通常也更重要。这是担心错误问题的典型症状。仅在开销不大的情况下(例如,将整个书建成字符串)时,方法的差异才变得重要。
徽州

7

它在很大程度上取决于每个新串联后新字符串的相对大小。对于+运算符,对于每个串联,都会创建一个新字符串。如果中间字符串相对较长,则+由于存储新的中间字符串而变得越来越慢。

考虑这种情况:

from time import time
stri=''
a='aagsdfghfhdyjddtyjdhmfghmfgsdgsdfgsdfsdfsdfsdfsdfsdfddsksarigqeirnvgsdfsdgfsdfgfg'
l=[]
#case 1
t=time()
for i in range(1000):
    stri=stri+a+repr(i)
print time()-t

#case 2
t=time()
for i in xrange(1000):
    l.append(a+repr(i))
z=''.join(l)
print time()-t

#case 3
t=time()
for i in range(1000):
    stri=stri+repr(i)
print time()-t

#case 4
t=time()
for i in xrange(1000):
    l.append(repr(i))
z=''.join(l)
print time()-t

结果

1 0.00493192672729

2 0.000509023666382

3 0.00042200088501

4 0.000482797622681

在1&2的情况下,我们添加了一个大字符串,join()的执行速度提高了约10倍。在情况3&4中,我们添加一个小字符串,并且'+'的执行速度稍快


3

我遇到了一种情况,我需要一个未知大小的可附加字符串。这些是基准测试结果(python 2.7.3):

$ python -m timeit -s 's=""' 's+="a"'
10000000 loops, best of 3: 0.176 usec per loop
$ python -m timeit -s 's=[]' 's.append("a")'
10000000 loops, best of 3: 0.196 usec per loop
$ python -m timeit -s 's=""' 's="".join((s,"a"))'
100000 loops, best of 3: 16.9 usec per loop
$ python -m timeit -s 's=""' 's="%s%s"%(s,"a")'
100000 loops, best of 3: 19.4 usec per loop

这似乎表明“ + =”是最快的。skymind链接的结果有些过时。

(我意识到第二个示例还不完整,最终列表将需要加入。但是,这确实表明,仅准备列表所花费的时间比字符串concat要长。)


我在第3次和第4次测试中获得了不到1秒的时间。你为什么得到这么高的时间?pastebin.com/qabNMCHS
bad_keypoints

@ronnieaka:他的所有测试时间都少于1秒。在第三和第四阶段,他的速度> 1 µs,而您没有。在这些测试中,我的速度也较慢(在Python 2.7.5,Linux上)。可能是CPU,版本,构建标志,谁知道。
Thanatos

这些基准测试结果毫无用处。特别是第一种情况,它没有进行任何字符串连接,只是完整地返回了第二个字符串值。
Antti Haapala

3

一年后,让我们用python 3.4.3测试mkoistinen的答案:

  • 加0.963564149000(速度为95.83%)
  • 加入0.923408469000(速度为100.00%)
  • 表格1.501130934000(速度为61.51%)
  • intp 1.019677452000(速度为90.56%)

没有改变。加入仍然是最快的方法。就可读性而言,可以说intp是最佳选择,但是您可能仍想使用intp。


1
也许它可以作为mkoistinen答案的补充,因为它比完整答案要差一点(或者至少要添加您正在使用的代码)。
Trilarion

1

受到@JasonBaker基准测试的启发,下面是一个比较10个"abcdefghijklmnopqrstuvxyz"字符串的简单示例,它显示了.join()更快的速度。即使变量有微小增加:

链状

>>> x = timeit.Timer(stmt='"abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz"')
>>> x.timeit()
0.9828147209324385

加入

>>> x = timeit.Timer(stmt='"".join(["abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz"])')
>>> x.timeit()
0.6114138159765048

看看这个问题的可接受答案(向下滚动很长):stackoverflow.com/questions/1349311/…–
mshsayem

1

对于一部分短字符串(即2个或3个不超过几个字符的字符串),加号的速度仍然更快。在Python 2和3中使用mkoistinen的出色脚本:

plus 2.679107467004 (100.00% as fast)
join 3.653773699996 (73.32% as fast)
form 6.594011374000 (40.63% as fast)
intp 4.568015249999 (58.65% as fast)

因此,当您的代码执行大量单独的小串联时,如果速度至关重要,则plus是首选方法。


1

可能“ Python 3.6中的新f字符串”是连接字符串的最有效方法。

使用%s

>>> timeit.timeit("""name = "Some"
... age = 100
... '%s is %s.' % (name, age)""", number = 10000)
0.0029734770068898797

使用.format

>>> timeit.timeit("""name = "Some"
... age = 100
... '{} is {}.'.format(name, age)""", number = 10000)
0.004015227983472869

使用f

>>> timeit.timeit("""name = "Some"
... age = 100
... f'{name} is {age}.'""", number = 10000)
0.0019175919878762215

资料来源:https : //realpython.com/python-f-strings/

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.