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

2025-02-28 08:22:00
admin
原创
53
摘要:问题描述:Python 中是否存在有效的批量字符串连接方法(如C# 中的StringBuilder或Java 中的StringBuffer )?我在这里发现了以下方法:使用简单连接+使用字符串列表和join方法UserString从MutableString模块使用使用字符数组和array模块cStringI...

问题描述:

Python 中是否存在有效的批量字符串连接方法(如C# 中的StringBuilder或Java 中的StringBuffer )?

我在这里发现了以下方法:

  • 使用简单连接+

  • 使用字符串列表和join方法

  • UserStringMutableString模块使用

  • 使用字符数组和array模块

  • cStringIOStringIO模块使用

应该使用什么以及为什么?

(相关问题在此处。)


解决方案 1:

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

基于这一点,如果您能将您的问题强行塞入模块中,该array模块可能''.join是最快的。但速度可能足够快,并且具有惯用性的优点,因此其他 Python 程序员更容易理解。

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

你可以使用该模块测量不同的方法timeit。它可以告诉你哪种方法最快,而不是让互联网上的陌生人随意猜测。

解决方案 2:

如果您事先知道所有组件,请使用Python 3.6 中引入的文字字符串插值,也称为f-strings格式化字符串

给出mkoistinen 答案中的测试用例,其中有字符串

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

在我的计算机上使用 Linux 上的 Python 3.6 进行的 IPython 和 timeit 模块计时,这些竞争者及其执行时间如下:

  • 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 µs(请注意,构建一个恒定长度的元组比构建一个恒定长度的列表稍快)。

因此,最短、最漂亮的代码也是最快的。


该速度可与 Python 2 的最快方法(即+我电脑上的连接方法)进行对比;如果是 8 位字符串,则需要0.203 µs,如果字符串都是 Unicode,则需要0.259 µs。

f''(在 Python 3.6 的 alpha 版本中,字符串的实现是最慢的- 实际上,生成的字节码几乎相当于''.join()不必要的调用的情况,str.__format__没有参数的调用只会返回self不变的结果。这些低效率问题在 3.6 最终版之前已经得到解决。)

解决方案 3:

''.join(sequence_of_strings)通常是最有效的——最简单、最快。

解决方案 4:

这取决于你在做什么。

在 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

...但请注意,您必须组合相对大量的字符串,差异才会变得明显。

解决方案 5:

根据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), ))

结果:

# Python 2.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 上总是更快。

# Python 2.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)

# Python 3.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)

# Python 3.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)

# Python 3.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)

经验教训:

  • 有时候,我的假设完全错误。

  • 针对系统环境进行测试。您将在生产环境中运行。

  • 字符串插值还没有消失!

总结:

  • 如果您使用 Python 2.6,请使用“+”运算符。

  • 如果您使用的是 Python 2.7,请使用“%”运算符。

  • 如果您使用的是 Python 3.x,请使用''.join()。

解决方案 6:

更新:Python3.11对 % 格式进行了一些优化,但可能还是坚持使用 f 字符串更好。

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

表现

对于大数据,图几乎相同。对于小数据,

表演 2

由 perfplot 拍摄,这是代码,大数据 == range(8),小数据 == range(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)],
)

当有中等数据时,有四个字符串x[0],,,x[1]而不是两个字符串x[2]x[3]

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]

表演 3

最好坚持使用f 字符串。另外%s的速度与.format()相似。

解决方案 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

0.000509023666382

3 0.00042200088501

4 0.000482797622681

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

解决方案 8:

我遇到了需要添加未知大小的可附加字符串的情况。这些是基准测试结果(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 链接的结果有点过时了。

(我意识到第二个例子并不完整。最终的列表需要连接起来。然而,这确实表明,简单地准备列表比字符串连接花费的时间更长。)

解决方案 9:

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

  • 加上 0.963564149000(速度提高 95.83%)

  • 加入 0.923408469000 (速度提高 100.00%)

  • 表格 1.501130934000 (速度提高 61.51%)

  • intp 1.019677452000 (速度提高 90.56%)

什么都没变。join仍然是最快的方法。鉴于字符串插值 ( intp ) 可以说是可读性方面的最佳选择,您可能仍然希望使用字符串插值。

解决方案 10:

可能“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

解决方案 11:

对于一小组短字符串(即不超过几个字符的 2 或 3 个字符串),plus**仍然更快。使用 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 就是首选方法。

解决方案 12:

受到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

解决方案 13:

嗯,这个问题困扰了我,而且我发现了一个差异很大的案例。

如果您需要附加到不断增长的字符串,则在某些条件下+=比其他所有方法好大约400 倍。有一种就地内存优化,名为BINARY_OP_INPLACE_ADD_UNICODE

这里的答案中有更多详细信息,这里的答案中有更深入的分析(推荐阅读!)。

与往常一样:使用您的版本和生产环境进行自己的分析和基准测试。

使用 CPython 3.12.4 测试:

import timeit

print()
print("## 100k iterations")

for i in range(0, 3):
    print("", timeit.timeit("string += string2; string += string3", "string='string1'; string2='string2'; string3='string3'", number=100_000), "string += string2; string += string3")
print()
for i in range(0, 3):
    print("", timeit.timeit("string += string2 + string3", "string='string1'; string2='string2'; string3='string3'", number=100_000), "string += string2 + string3")
print()
for i in range(0, 3):
    print("", timeit.timeit("string = string + string2 + string3", "string='string1'; string2='string2'; string3='string3'", number=100_000), "string = string + string2 + string3")
print()
for i in range(0, 3):
    print("", timeit.timeit("string = ''.join((string, string2, string3))", "string='string1'; string2='string2'; string3='string3'", number=100_000), "string = ''.join((string, string2, string3))")
print()
for i in range(0, 3):
    print("", timeit.timeit("string = f'{string}{string2}{string3}'", "string='string1'; string2='string2'; string3='string3'", number=100_000), "string = f'{string}{string2}{string3}'")
## 100k iterations
 0.009063743986189365 string += string2; string += string3
 0.0086502339981962 string += string2; string += string3
 0.008066322014201432 string += string2; string += string3

 0.008959753991803154 string += string2 + string3
 0.008912532997783273 string += string2 + string3
 0.008995934011181816 string += string2 + string3

 6.716721252974821 string = string + string2 + string3
 6.397386430006009 string = string + string2 + string3
 6.404028512974037 string = string + string2 + string3

 3.60368204099359 string = ''.join((string, string2, string3))
 3.1406521030003205 string = ''.join((string, string2, string3))
 3.161398060998181 string = ''.join((string, string2, string3))

 3.119284204003634 string = f'{string}{string2}{string3}'
 3.1289724090020172 string = f'{string}{string2}{string3}'
 3.1352513500023633 string = f'{string}{string2}{string3}'

解决方案 14:

=>连接字符串的最佳方式是使用“+”。例如:-

Print(string1 +string2)

=>另一种简单的方法是使用联合方法。例如:-

.joint()
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2941  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1803  
  PLM(产品生命周期管理)系统在企业的产品研发、生产与管理过程中扮演着至关重要的角色。然而,在实际运行中,资源冲突是经常会遇到的难题。资源冲突可能导致项目进度延迟、成本增加以及产品质量下降等一系列问题,严重影响企业的效益与竞争力。因此,如何有效应对PLM系统中的资源冲突,成为众多企业关注的焦点。接下来,我们将详细探讨5...
plm项目管理系统   31  
  敏捷项目管理与产品生命周期管理(PLM)的融合,正成为企业在复杂多变的市场环境中提升研发效率、增强竞争力的关键举措。随着技术的飞速发展和市场需求的快速更迭,传统的研发流程面临着诸多挑战,而将敏捷项目管理理念融入PLM,有望在2025年实现研发流程的深度优化,为企业创造更大的价值。理解敏捷项目管理与PLM的核心概念敏捷项...
plm项目   31  
  模块化设计在现代产品开发中扮演着至关重要的角色,它能够提升产品开发效率、降低成本、增强产品的可维护性与可扩展性。而产品生命周期管理(PLM)系统作为整合产品全生命周期信息的关键平台,对模块化设计有着强大的支持能力。随着技术的不断发展,到 2025 年,PLM 系统在支持模块化设计方面将有一系列令人瞩目的技术实践。数字化...
plm软件   28  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用