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

msh*_*yem 131 python string

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

  • 使用简单连接 +
  • 使用字符串列表和join方法
  • 使用UserString来自MutableString模块
  • 使用字符数组和array模块
  • 使用cStringIO来自StringIO模块

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

[ 这里的相关问题 ]

Joh*_*uhy 117

您可能对此感兴趣:Guido 的优化轶事.虽然值得记住的是,这是一篇旧文章,它早于存在的事情''.join(虽然我猜string.joinfields是或多或少相同)

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

最后,优化的黄金法则:不要优化,除非你知道你需要,并测量而不是猜测.

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

  • "在你知道自己需要之前不要进行优化." 除非你只是使用一个名义上不同的习语,并且可以避免重复编写代码而不需要额外的努力. (2认同)
  • 对于`.join()`来说,“足够快”意味着什么?主要问题是,是否 a) 创建用于串联的字符串副本(类似于 `s = s + 'abc'`),这需要 O(n) 运行时间,或者 b) 简单地附加到现有字符串而不创建一个副本,需要 O(1)? (2认同)
  • @CGFoX `s = s + 'abc'` 创建一个全新的 `str` 对象,然后使 `s` 引用该对象,而不是 `s` 引用的原始对象。如果在循环内执行此操作,则会重复将“s”的(越来越长的)值复制到一系列新对象中。然而,`''.join` 在 `str` 类型的“内部”运行。它只需访问操作数的内容一次,即可复制到预先分配的“str”对象中,该对象足够大以容纳结果。 (2认同)

Ale*_*lli 58

''.join(sequenceofstrings) 通常效果最好 - 最简单,最快捷.

  • @mshsayem:`"".join(chr(x)for x in xrange(65,91))`---在这种情况下,join的参数是一个迭代器,通过生成器表达式创建.没有构建的临时列表. (7认同)
  • @mshsayem,在Python中,序列可以是任何可枚举对象,甚至是函数. (3认同)
  • 我非常喜欢'''.join(序列)`成语.生成以逗号分隔的列表特别有用:`','.join([1,2,3])`给出字符串''1,2,3'. (2认同)
  • @balpha:然而生成器版本比列表理解版本慢:C:\ temp> python -mtimeit"''.join(xr(x)中的x的chr(x)(65,91))"100000循环,最好的3:9.71 usec每个循环C:\ temp> python -mtimeit"''.join([chr(x)for x in xrange(65,91)])"100000循环,最佳3:7.1 usec每循环 (2认同)

Ant*_*ala 45

Python 3.6使用Literal String Interpolation改变了已知组件的字符串连接游戏.

鉴于mkoistinen的答案中的测试用例,有字符串

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'
Run Code Online (Sandbox Code Playgroud)

竞争者是

  • f'http://{domain}/{lang}/{path}'- 0.151μs

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

  • 'http://' + domain + '/' + lang + '/' + path - 0.356μs

  • ''.join(('http://', domain, '/', lang, '/', path))- 0.249μs(注意构建一个恒定长度的元组比构建一个常量列表略快).

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

在Python的alpha版本中,f''字符串的实现是最慢的 - 实际上生成的字节代码几乎等同于''.join()不必要的调用str.__format__,没有参数的情况只会self保持不变.这些低效率在3.6决赛前得到解决.

速度可以与Python 2的最快方法形成对比,这是+我计算机上的连接; 对于8位字符串,它需要0.203μs;如果字符串都是Unicode ,则需要0.259μs.

  • 这是“哪个最快”问题的实际答案。当前接受的答案过于固执地认为这是一种过早的优化,以至于它甚至不去测试各种可用的技术。 (8认同)
  • 截至 3.10.0,排名相对于彼此没有变化。我的计时是 f-string 110 ns,printf-style w/`%` 160 ns,concat w/`+` 176 ns,以及 `''.join` 130 ns(我使用了 `%%timeit` 魔法,其中 `domain `、`lang` 和 `path` 都在设置步骤(第一行)中定义,并且要测试的代码没有包装在函数中,否则最大限度地减少与正在测试的操作无关的开销)。请注意,您可以使用 `'/'.join(('http:/', domain, lang, path))` (在 99 ns 时)击败 f 字符串,但这既不漂亮也不具有普遍性。 (2认同)

Jas*_*ker 37

这取决于你在做什么.

在Python 2.5之后,使用+运算符的字符串连接非常快.如果您只是连接几个值,使用+运算符效果最佳:

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

>>> x = timeit.Timer(stmt="''.join(['a', 'b'])")
>>> x.timeit()
0.76200008392333984
Run Code Online (Sandbox Code Playgroud)

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

>>> 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
Run Code Online (Sandbox Code Playgroud)

...但请注意,在差异变得明显之前,您必须将相对较多的字符串组合在一起.

  • 1)在你的第一次测量中,它可能是花费时间的列表构造.尝试使用元组.2)CPython表现一致,但是其他Python实现在+和+ =时表现更差 (2认同)

mko*_*nen 15

根据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), ))
Run Code Online (Sandbox Code Playgroud)

结果:

# 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)
Run Code Online (Sandbox Code Playgroud)

如果我使用较短的域和较短的路径,插值仍然会胜出.但是,使用更长的字符串,差异更明显.

现在我有一个不错的测试脚本,我也在Python 2.6,3.3和3.4下进行了测试,这是结果.在Python 2.6中,plus运算符是最快的!在Python 3上,加入胜出.注意:这些测试在我的系统上非常可重复.因此,'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)
Run Code Online (Sandbox Code Playgroud)

学过的知识:

  • 有时,我的假设是错误的.
  • 针对系统环境进行测试 你将在生产中运行.
  • 字符串插值还没死!

TL;博士:

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

  • 注意:文字字符串插值对于 3.6+ 仍然更快:[`f'http://{domain}/{lang}/{path}'`](https://repl.it/HaRm) (2认同)

小智 10

它在很大程度上取决于每次新连接后新字符串的相对大小。使用+运算符,每次连接都会生成一个新字符串。如果中间字符串相对较长,则+速度会越来越慢,因为正在存储新的中间字符串。

考虑这个案例:

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
Run Code Online (Sandbox Code Playgroud)

结果

1 0.00493192672729

2 0.000509023666382

3 0.00042200088501

4 0.000482797622681

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


Mac*_*exa 5

对于 python 3.8.6/3.9,我不得不做一些肮脏的黑客攻击,因为 perfplot 给出了一些错误。这里假设x[0]是 aa并且x[1]b表现

对于大数据,该图几乎相同。对于小数据, 性能2

由 perfplot 获取,这是代码,大数据 == 范围(8),小数据 == 范围(4)。

import perfplot

from random import choice
from string import ascii_lowercase as letters

def generate_random(x):
    data = ''.join(choice(letters) for i in range(x))
    sata = ''.join(choice(letters) for i in range(x))
    return [data,sata]

def fstring_func(x):
    return [ord(i) for i in f'{x[0]}{x[1]}']

def format_func(x):
    return [ord(i) for i in "{}{}".format(x[0], x[1])]

def replace_func(x):
    return [ord(i) for i in "|~".replace('|', x[0]).replace('~', x[1])]

def join_func(x):
    return [ord(i) for i in "".join([x[0], x[1]])]

perfplot.show(
    setup=lambda n: generate_random(n),
    kernels=[
        fstring_func,
        format_func,
        replace_func,
        join_func,
    ],
    n_range=[int(k ** 2.5) for k in range(4)],
)
Run Code Online (Sandbox Code Playgroud)

当有中等数据并且有 4 个字符串时,x[0], x[1], x[2],x[3]而不是 2 个字符串:

def generate_random(x):
    a =  ''.join(choice(letters) for i in range(x))
    b =  ''.join(choice(letters) for i in range(x))
    c =  ''.join(choice(letters) for i in range(x))
    d =  ''.join(choice(letters) for i in range(x))
    return [a,b,c,d]
Run Code Online (Sandbox Code Playgroud)

性能 3 最好坚持使用 fstrings。%s 的速度也类似于 .format()