在Python中连接字符串的首选方式是什么?

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

我可以这样写:

s += stringfromelsewhere

或者像这样:

s = []


s.append(somestring)
    

# later
    

s = ''.join(s)

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

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

但它在Python 2.x中。,所以问题是Python 3中有什么变化吗?

788677 次浏览

推荐的方法仍然是使用追加和连接。

如果要连接很多值,则两者都不使用。附加列表的开销很大。你可以使用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 FAQ: 将许多字符串连接在一起的最有效的方法是什么?

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

要积累许多str对象,推荐的习惯用法是放置它们 将str.join()放入一个列表,并在结束时调用str.join():

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

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

要积累许多字节对象,推荐的习惯用法是扩展a 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

虽然有些过时,但像Pythonista一样编写代码:地道的Python推荐join()而不是+ 在本节中。正如PythonSpeedPerformanceTips在其字符串连接部分中所做的那样,带有以下免责声明:

这一节的准确性与后面的内容有关 Python的不同版本。在CPython 2.5中,字符串连接是公平的 虽然这可能不适用于其他Python 实现。有关讨论请参见ConcatenationTestCode

最好的将字符串附加到字符串变量的方法是使用++=。这是因为它可读性强,速度快。它们也一样快,你选择哪一种取决于你的口味,后者是最常见的。下面是timeit模块的计时:

a = a + b:
0.11338996887207031
a += b:
0.11040496826171875
然而,那些建议拥有列表并将其追加到列表中然后加入这些列表的人,这样做是因为与扩展字符串相比,将字符串追加到列表中可能非常快。在某些情况下,这可能是真的。例如,这里是一个 一个单字符字符串的百万次追加,首先是字符串,然后是列表:

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

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

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

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

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

a.join(a):
0.43739795684814453

牛津大学出版社。事实证明,即使在这种情况下,append/join也更慢。

那么这个建议从何而来呢?Python 2 ?

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

好吧,如果你使用的是非常长的字符串,那么append/join是更快(通常情况下你不会,你会有一个内存为100MB的字符串吗?)

但真正的关键是Python 2.3。我甚至不会给你们看计时,因为它太慢了,还没有完成。这些测试突然采取分钟。除了append/join,它和后面的python一样快。

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

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

然而,这是CPython。其他实现可能有其他问题。这就是为什么过早优化是万恶之源的另一个原因。不要使用被认为“更快”的技术,除非你先测量过它。

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

为什么我在代码中使用了大量的追加/连接?因为有时候会更清楚。特别是当你要连接在一起的东西应该用空格、逗号或换行符分隔。

在稳定和交叉实现方面,通过“+”来使用字符串连接是最糟糕的连接方法,因为它不支持所有值。PEP8标准不鼓励这样做,并鼓励长期使用format()、join()和append()。

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

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

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

s += "%s" %(stringfromelsewhere)

如果要连接的字符串是字面量,则使用字符串字面值连接

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

如果你想对字符串的一部分进行注释(如上所述),或者如果你想对部分文字使用原始字符串或三引号,但不是全部,这很有用。

因为这发生在语法层,所以它使用零连接操作符。

写出这个函数

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

我的用例略有不同。我必须构造一个查询,其中超过20个字段是动态的。 我使用format method

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

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

在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.'

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

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

我将展示一个真实世界的代码示例,它天真地依赖于+=这种优化,但它并不适用。下面的代码将短字符串的可迭代对象转换为更大的块,以便在批量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()

micro-benchmark

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

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("{0}{1}".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

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