在 Python 中连接字符串的首选方法是哪种?

     2023-02-15     10

关键词:

【中文标题】在 Python 中连接字符串的首选方法是哪种?【英文标题】:Which is the preferred way to concatenate a string in Python? 【发布时间】:2012-08-23 14:03:07 【问题描述】:

由于 Python 的 string 无法更改,我想知道如何更有效地连接字符串?

我可以这样写:

s += stringfromelsewhere

或者像这样:

s = []

s.append(somestring)
    
# later
    
s = ''.join(s)

在写这个问题的时候,我发现了一篇关于这个话题的好文章。

http://www.skymind.com/~ocrow/python_string/

但它是在 Python 2.x. 中,所以问题是在 Python 3 中是否发生了一些变化?

【问题讨论】:

***.com/questions/10043636/… 【参考方案1】:

推荐的方法还是使用append和join。

【讨论】:

正如您从我的回答中看到的,这取决于您要连接的字符串数量。我已经对此进行了一些计时(请参阅我在我的 cmets 中链接到我的答案的谈话),通常除非超过十个,否则使用 +。 PEP8 提到了这一点 (python.org/dev/peps/pep-0008/#programming-recommendations)。理由是,虽然 CPython 对使用 += 的字符串连接进行了特殊优化,但其他实现可能没有。【参考方案2】:

如果您要连接很多值,那么两者都不是。附加列表是昂贵的。您可以为此使用 StringIO。尤其是如果您要通过大量操作来构建它。

from cStringIO import StringIO
# python3:  from io import StringIO

buf = StringIO()

buf.write('foo')
buf.write('foo')
buf.write('foo')

buf.getvalue()
# 'foofoofoo'

如果您已经从其他操作返回了完整列表,那么只需使用''.join(aList)

来自 python 常见问题解答:What is the most efficient way to concatenate many strings together?

str 和 bytes 对象是不可变的,因此可以连接许多 串在一起是低效的,因为每个连接都会创建一个新的 目的。在一般情况下,总运行时间成本是二次方 总字符串长度。

要积累很多 str 对象,推荐的习惯用法是放置它们 进入列表并在最后调用 str.join() :

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

(另一个相当有效的习惯用法是使用 io.StringIO)

要积累很多字节对象,推荐的习惯用法是扩展一个 使用就地连接(+= 运算符)的 bytearray 对象:

result = bytearray()
for b in my_bytes_objects:
    result += b

编辑:我很傻,结果向后粘贴,看起来附加到列表比 cStringIO 快。我还添加了对 bytearray/str concat 的测试,以及使用更大字符串的更大列表的第二轮测试。 (python 2.7.3)

大型字符串列表的 ipython 测试示例

try:
    from cStringIO import StringIO
except:
    from io import StringIO

source = ['foo']*1000

%%timeit buf = StringIO()
for i in source:
    buf.write(i)
final = buf.getvalue()
# 1000 loops, best of 3: 1.27 ms per loop

%%timeit out = []
for i in source:
    out.append(i)
final = ''.join(out)
# 1000 loops, best of 3: 9.89 ms per loop

%%timeit out = bytearray()
for i in source:
    out += i
# 10000 loops, best of 3: 98.5 µs per loop

%%timeit out = ""
for i in source:
    out += i
# 10000 loops, best of 3: 161 µs per loop

## Repeat the tests with a larger list, containing
## strings that are bigger than the small string caching 
## done by the Python
source = ['foo']*1000

# cStringIO
# 10 loops, best of 3: 19.2 ms per loop

# list append and join
# 100 loops, best of 3: 144 ms per loop

# bytearray() +=
# 100 loops, best of 3: 3.8 ms per loop

# str() +=
# 100 loops, best of 3: 5.11 ms per loop

【讨论】:

cStringIO 在 Py3 中不存在。请改用io.StringIO 至于为什么重复追加到字符串会很昂贵:joelonsoftware.com/articles/fog0000000319.html 等什么?当您说“附加列表 [很昂贵]”时,您的意思是“附加字符串”对吗? @khuongduybui 它可能应该说“附加到列表很昂贵”【参考方案3】:

虽然有些过时,Code Like a Pythonista: Idiomatic Python 推荐 join() 而不是 + in this section。与PythonSpeedPerformanceTips 在其关于string concatenation 的部分中一样,带有以下免责声明:

本节的准确性存在争议 Python 的版本。在 CPython 2.5 中,字符串连接相当 很快,尽管这可能不适用于其他 Python 实施。请参阅 ConcatenationTestCode 进行讨论。

【讨论】:

【参考方案4】:

将字符串附加到字符串变量的最佳方法是使用++=。这是因为它可读且快速。它们也一样快,您选择哪一种是口味问题,后者是最常见的。以下是 timeit 模块的时间安排:

a = a + b:
0.11338996887207031
a += b:
0.11040496826171875

但是,那些建议使用列表并附加到它们然后加入这些列表的人这样做是因为与扩展字符串相比,将字符串附加到列表可能非常快。在某些情况下,这可能是真的。例如,这里是一个 百万个单字符字符串的追加,首先追加到字符串,然后追加到列表:

a += b:
0.10780501365661621
a.append(b):
0.1123361587524414

好的,事实证明即使结果字符串有一百万个字符长,追加仍然更快。

现在让我们尝试将一千个字符的长字符串附加十万次:

a += b:
0.41823482513427734
a.append(b):
0.010656118392944336

因此,结束字符串的长度约为 100MB。那很慢,附加到列表要快得多。该时间不包括最终的a.join()。那么这需要多长时间?

a.join(a):
0.43739795684814453

麻烦。事实证明,即使在这种情况下,追加/加入也更慢。

那么这个建议是从哪里来的呢? Python 2?

a += b:
0.165287017822
a.append(b):
0.0132720470428
a.join(a):
0.114929914474

好吧,如果您使用极长的字符串(通常不会,那么内存中的 100MB 字符串会是什么?)

但真正的关键是 Python 2.3。我什至不会告诉你时间,因为它太慢了,它还没有完成。这些测试突然需要 分钟。除了追加/连接,它和后来的 Python 一样快。

是的。在石器时代,Python 中的字符串连接非常缓慢。但是在 2.4 上它不再是(或者至少是 Python 2.4.7),所以使用 append/join 的建议在 2008 年已经过时了,当时 Python 2.3 停止更新,你应该停止使用它。 :-)

(更新:当我更仔细地进行测试时发现,在 Python 2.3 上使用 ++= 对两个字符串也更快。建议使用 ''.join() 一定是一种误解)

但是,这是 CPython。其他实现可能有其他问题。这只是过早优化是万恶之源的另一个原因。除非您先进行测量,否则不要使用所谓的“更快”的技术。

因此,进行字符串连接的“最佳”版本是使用 + 或 +=。如果这对你来说很慢,这不太可能,那就做点别的吧。

那么为什么我要在我的代码中使用大量的附加/连接呢?因为有时它实际上更清楚。特别是当你应该连接在一起的任何东西都应该用空格、逗号或换行符分隔时。

【讨论】:

如果你有多个字符串 (n > 10) "".join(list_of_strings) 还是更快 += 速度快的原因是,如果 refcount 为 1,cpython 中会出现性能问题——它在几乎所有其他 python 实现中都会崩溃(除了相当特殊的配置pypy 构建) 为什么这么多人赞成?使用仅在一种特定实现上有效并且本质上相当于脆弱的黑客攻击来修复二次时间算法的算法如何更好?此外,您完全误解了“过早的优化是万恶之源”的观点。那句话是在谈论小优化。这是从 O(n^2) 到 O(n) 这不是一个小的优化。 这是实际的引语:“我们应该忘记小的效率,比如大约 97% 的时间:过早的优化是万恶之源。但我们不应该放弃我们在关键时刻的机会3%。一个好的程序员不会因为这样的推理而自满,他会明智地仔细查看关键代码;但只有在识别出该代码之后” 没有人说 a + b 很慢。当你不止一次做 a = a + b 时,它是二次的。 a + b + c 并不慢,我重复 not slow 因为它只需要遍历每个字符串一次,而它必须使用 a = a + b 方法多次重新遍历之前的字符串(假设这是在某种循环中)。记住字符串是不可变的。【参考方案5】:

就稳定性和交叉实现而言,使用“+”就地字符串连接是最糟糕的连接方法,因为它不支持所有值。 PEP8 standard 不鼓励这样做,并鼓励使用 format()、join() 和 append() 以供长期使用。

引用自链接的“编程建议”部分:

例如,对于 a += b 或 a = a + b 形式的语句,不要依赖 CPython 对就地字符串连接的高效实现。即使在 CPython 中,这种优化也很脆弱(它只适用于某些类型),并且在不使用引用计数的实现中根本不存在。在库的性能敏感部分,应该使用 ''.join() 形式。这将确保连接在各种实现中以线性时间发生。

【讨论】:

参考链接会很好:) 多么荒谬的情况。这是人们首先学会如何做的事情之一,在这里,象牙塔里的巫师发布了 PEP 阻止它,因为它很脆弱。【参考方案6】:

你也可以使用这个(更有效)。 (https://softwareengineering.stackexchange.com/questions/304445/why-is-s-better-than-for-concatenation)

s += "%s" %(stringfromelsewhere)

【讨论】:

【参考方案7】:

如果您要连接的字符串是文字,请使用String literal concatenation

re.compile(
        "[A-Za-z_]"       # letter or underscore
        "[A-Za-z0-9_]*"   # letter, digit or underscore
    )

如果您想对字符串的一部分进行评论(如上),或者如果您想使用raw strings 或三引号来表示文字的一部分但不是全部,这很有用。

由于这发生在语法层,它使用零连接运算符。

【讨论】:

【参考方案8】:

你写这个函数

def str_join(*args):
    return ''.join(map(str, args))

然后你可以在任何你想打电话的地方简单地打电话

str_join('Pine')  # Returns : Pine
str_join('Pine', 'apple')  # Returns : Pineapple
str_join('Pine', 'apple', 3)  # Returns : Pineapple3

【讨论】:

str_join = lambda *str_list: ''.join(s for s in str_list)【参考方案9】:

我的用例略有不同。我必须构建一个查询,其中超过 20 个字段是动态的。 我遵循了这种使用格式方法的方法

query = "insert into 0(1,2,3) values(4, 5, 6)"
query.format('users','name','age','dna','suzan',1010,'nda')

这对我来说相对简单,而不是使用 + 或其他方式

【讨论】:

【参考方案10】:

在 Python >= 3.6 中,新的 f-string 是连接字符串的有效方法。

>>> name = 'some_name'
>>> number = 123
>>>
>>> f'Name is name and the number is number.'
'Name is some_name and the number is 123.'

【讨论】:

如果f'ab' 没有比a += ba + b更有效率,我看不出这对专门询问的问题有何意义表现。此功能是语法糖(当然是好且有用的糖!),而不是性能优化。【参考方案11】:

正如@jdi 提到的那样,Python 文档建议使用str.joinio.StringIO 进行字符串连接。并说开发人员应该期望循环中的+= 的二次时间,即使自 Python 2.4 以来有优化。正如this 回答所说:

如果 Python 检测到左侧参数没有其他引用,它会调用 realloc 来尝试通过调整字符串大小来避免复制。这不是你应该依赖的东西,因为它是一个实现细节,而且如果 realloc 最终需要频繁移动字符串,性能无论如何都会降低到 O(n^2)。

我将展示一个天真地依赖+= 这种优化的实际代码示例,但它并不适用。下面的代码将可迭代的短字符串转换为更大的块,以在批量 API 中使用。

def test_concat_chunk(seq, split_by):
    result = ['']
    for item in seq:
        if len(result[-1]) + len(item) > split_by: 
            result.append('')
        result[-1] += item
    return result

由于二次时间复杂度,此代码可以运行数小时。以下是建议数据结构的替代方案:

import io

def test_stringio_chunk(seq, split_by):
    def chunk():
        buf = io.StringIO()
        size = 0
        for item in seq:
            if size + len(item) <= split_by:
                size += buf.write(item)
            else:
                yield buf.getvalue()
                buf = io.StringIO()
                size = buf.write(item)
        if size:
            yield buf.getvalue()

    return list(chunk())

def test_join_chunk(seq, split_by):
    def chunk():
        buf = []
        size = 0
        for item in seq:
            if size + len(item) <= split_by:
                buf.append(item)
                size += len(item)
            else:
                yield ''.join(buf)                
                buf.clear()
                buf.append(item)
                size = len(item)
        if size:
            yield ''.join(buf)

    return list(chunk())

还有一个微基准:

import timeit
import random
import string
import matplotlib.pyplot as plt

line = ''.join(random.choices(
    string.ascii_uppercase + string.digits, k=512)) + '\n'
x = []
y_concat = []
y_stringio = []
y_join = []
n = 5
for i in range(1, 11):
    x.append(i)
    seq = [line] * (20 * 2 ** 20 // len(line))
    chunk_size = i * 2 ** 20
    y_concat.append(
        timeit.timeit(lambda: test_concat_chunk(seq, chunk_size), number=n) / n)
    y_stringio.append(
        timeit.timeit(lambda: test_stringio_chunk(seq, chunk_size), number=n) / n)
    y_join.append(
        timeit.timeit(lambda: test_join_chunk(seq, chunk_size), number=n) / n)
plt.plot(x, y_concat)
plt.plot(x, y_stringio)
plt.plot(x, y_join)
plt.legend(['concat', 'stringio', 'join'], loc='upper left')
plt.show()

【讨论】:

【参考方案12】:

你可以用不同的方式来做。

str1 = "Hello"
str2 = "World"
str_list = ['Hello', 'World']
str_dict = 'str1': 'Hello', 'str2': 'World'

# Concatenating With the + Operator
print(str1 + ' ' + str2)  # Hello World

# String Formatting with the % Operator
print("%s %s" % (str1, str2))  # Hello World

# String Formatting with the   Operators with str.format()
print("".format(str1, str2))  # Hello World
print("01".format(str1, str2))  # Hello World
print("str1 str2".format(str1=str_dict['str1'], str2=str_dict['str2']))  # Hello World
print("str1 str2".format(**str_dict))  # Hello World

# Going From a List to a String in Python With .join()
print(' '.join(str_list))  # Hello World

# Python f'strings --> 3.6 onwards
print(f"str1 str2")  # Hello World

我通过以下文章创建了这个小摘要。

Python 3's f-Strings: An Improved String Formatting Syntax (Guide)(还包括速度测试) Formatted string literals String Concatenation and Formatting Splitting, Concatenating, and Joining Strings in Python

【讨论】:

热敏电阻高精度的读取方法是哪种啊?

...制供暖,通风和空调系统。这些应用中,常用的温度测量方法是使用负温度系数(NTC)热敏电阻。NTC是电阻器件,其电阻随着温度的改变而改变。为了满足当今温度传感器需求,一种更新、更高效、更准确的方法是使用硅基热敏... 查看详情

哪种方法更喜欢在 MongoDB 中动态管理数据库请求?

...】:2020-08-0912:32:04【问题描述】:在动态创建与数据库的连接的情况下,我在搜索互联网后提出了以下两种方法。在后端使用LRU队列算法管理不同的数据库连接。(连接池)使用单一连接,并使用猫鼬的useDB功能在数据库之 查看详情

你是哪种设计师?

上一篇中向大家介绍了用户调查设计师们的工作。其实设计师的工作不只是停留在幕后,即使是软件行业中,也有这样一种设计师,他们就是需要站出来与客户打交道的。MarketingDesigner也就是营销设计师,虽然是一个相对比较新... 查看详情

在 Python 中拆分连接字符串的正确而优雅的方法

】在Python中拆分连接字符串的正确而优雅的方法【英文标题】:TherightandelegantwaytosplitajoinastringinPython【发布时间】:2012-10-0203:45:45【问题描述】:我有以下清单:>>>poly\'C:\\\\04-las_clip_inside_area\\\\16x16grids_1pp_fsa.shp\'>>>... 查看详情

indexof()常用方法(代码片段)

今天来小结一下indexOf()方法的几种API首选自定义一个字符串(生活就像一盒巧克力,你永远不会知道你要拿起的是哪一个——《阿甘正传》)Stringstr="lifewaslikeaboxofchocolatesyouneverknowwhatyouaregonnaget";1.indexOf(Stringstr)说明:从前往后查... 查看详情

在 Python 中实现钩子或回调的首选方法是啥?

】在Python中实现钩子或回调的首选方法是啥?【英文标题】:What\'sthepreferredwaytoimplementahookorcallbackinPython?在Python中实现钩子或回调的首选方法是什么?【发布时间】:2011-05-1714:43:24【问题描述】:我想通过提供一个接口来调用用... 查看详情

Python:在同一行中打印带有首选小数位数的字符串和数字

】Python:在同一行中打印带有首选小数位数的字符串和数字【英文标题】:Python:Printstringandnumberwithapreferrednumberofdecimalplacesinthesameline【发布时间】:2020-05-0511:01:59【问题描述】:我想打印一个字符串加上一个指定小数位数的浮点... 查看详情

浏览器使用哪种等宽字体?

...定font-family:monospace;,我的理解是浏览器会选择它的默认/首选等宽字体。如果正确,您如何确定您的浏览器使用的是哪种等宽字体?【问题讨论】:取决于操作系统和安装的字体。等宽​​是常用的一种,它为每个字符使用相同... 查看详情

python中汉字和变量拼接输出的8种方法(字符串拼接)

参考技术A方法1:使用加号“+”连接字符串最常用的连接字符串的方式是用加号“+”连接两个字符串,连接后这两个字符串将连接成一个字符串。但需注意的是,不能用“+”连接字符串和数字,需要把数字使用str()函数转换成... 查看详情

怎么才能通过文件的后缀名看出文件是哪种文件?

怎样才能通过文件的后缀名看出文件是哪种文件,怎么打开,请各位大侠指教,谢谢!文件后缀名详解★什么是文件的后缀名说起来Windows工作界面下的文件名简直是随心所欲,比如:某编辑部的2000年工作计划。文件名即可用中... 查看详情

ActiveSupport::Memoizable 指的是哪种 Ruby memoize 模式?

】ActiveSupport::Memoizable指的是哪种Rubymemoize模式?【英文标题】:WhichRubymemoizepatterndoesActiveSupport::Memoizablereferto?【发布时间】:2012-02-2606:43:33【问题描述】:所以在Rails3.2中,ActiveSupport::Memoizable已被弃用。消息内容如下:DEPRECATIONW... 查看详情

在 Go 中实例化类型的首选方式

...欢Go没有给我一百万种方法来做简单事情的事实——借用Python之禅,“应该有一种——最好只有一种——显而易见的方法。”但是,我不清楚实例化类型的首选/惯用方式。基本类型很简单:n:=0t:=1.5str:="Hello"但是结构呢?以下是... 查看详情

在python中连接字符串和整数

】在python中连接字符串和整数【英文标题】:ConcatenatingstringandintegerinPython【发布时间】:2012-07-1812:47:04【问题描述】:在python中说你有s="string"i=0prints+i会给你错误所以你写prints+str(i)不会出错。我认为这是处理int和string连接的一... 查看详情

在 sql 数据库中创建唯一列 id 的首选方法

...中造成问题,所以我想关于使用phpRand(),然后将其转换为字符串,然后将其插入数据库, 查看详情

VBA 字符串空间:首选方法?

】VBA字符串空间:首选方法?【英文标题】:VBAStringSpaces:PreferredMethod?【发布时间】:2014-12-2302:14:26【问题描述】:连接字符串时,有多种插入空格的有效方法。空间函数:Print"A"&Space(1)&"B"ABVS引述:Print"A"&""&"B"ABVSChr... 查看详情

在活动记录库中添加某些方法的首选方法是啥?

】在活动记录库中添加某些方法的首选方法是啥?【英文标题】:Whatisthepreferredwaytoaddsomemethodinactiverecordbase?在活动记录库中添加某些方法的首选方法是什么?【发布时间】:2014-09-2401:41:32【问题描述】:我想创建一个模块,为... 查看详情

java排序,效率高的是哪种排序方法

java排序,效率高的是哪种排序方法和所有其他语言是一样的。应该还是快速排序效率最高。publicstaticvoidbubbleSort(inta[])intlen=a.length;for(inti=0;i<len-1;i++)for(intj=0;j<len-1-i;j++)if(a[j]>a[j+1])inttemp=a[j];a[j]=a[j+1];a[j+1]=temp;publicstaticvoidsele... 查看详情

在数据库中存储密码的首选方法

】在数据库中存储密码的首选方法【英文标题】:PreferredMethodofStoringPasswordsInDatabase【发布时间】:2010-10-1113:41:38【问题描述】:/数据类型是什么(最好是SQLServer2005)。我在我们的几个应用程序中一直这样做的方式是首先使用.NE... 查看详情