在 Python 中连接字符串的首选方法是什么?[重复]
- 2024-12-13 08:36:00
- admin 原创
- 145
问题描述:
由于 Pythonstring
无法改变,我想知道如何更有效地连接字符串?
我可以这样写:
s += stringfromelsewhere
或者像这样:
s = []
s.append(somestring)
# later
s = ''.join(s)
在写这个问题时,我发现了一篇讨论该主题的好文章。
http://www.skymind.com/~ocrow/python_string/
但它是在 Python 2.x 中,所以问题是 Python 3 中是否有所改变?
解决方案 1:
将字符串附加到字符串变量的最佳方法是使用+
或+=
。这是因为它可读性强且速度快。它们的速度也一样快,选择哪一个取决于个人喜好,后者是最常见的。以下是模块的计时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 上,使用+
and+=
对两个字符串来说也更快。使用 and 的建议''.join()
肯定是一个误解)
但是,这是 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 常见问题解答:将多个字符串连接在一起的最有效方法是什么?
str 和 bytes 对象是不可变的,因此将多个字符串连接在一起效率很低,因为每次连接都会创建一个新对象。一般情况下,总运行时成本是字符串总长度的二次方。
要累积许多 str 对象,建议的做法是将它们放入列表中并在最后调用 str.join():
chunks = [] for s in my_strings: chunks.append(s) result = ''.join(chunks)
(另一个合理有效的习惯用法是使用 io.StringIO)
为了累积许多字节对象,建议使用就地连接(+= 运算符)来扩展字节数组对象:
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
解决方案 3:
在 Python >= 3.6 中,新的f 字符串是连接字符串的有效方法。
>>> name = 'some_name'
>>> number = 123
>>>
>>> f'Name is {name} and the number is {number}.'
'Name is some_name and the number is 123.'
解决方案 4:
使用 '+' 进行字符串连接是稳定性和跨实现方面最糟糕的连接方法,因为它不支持所有值。PEP8标准不鼓励这样做,并鼓励长期使用 format()、join() 和 append()。
正如链接的“编程建议”部分所引用的:
例如,对于 a += b 或 a = a + b 形式的语句,不要依赖 CPython 就地字符串连接的有效实现。这种优化即使在 CPython 中也很脆弱(它只适用于某些类型),并且在未使用引用计数的实现中根本不存在。在库的性能敏感部分,应改用 ''.join() 形式。这将确保连接在各种实现中以线性时间发生。
解决方案 5:
您可以采用不同的方式来进行。
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
我通过以下文章创建了这个小总结。
Python 3 的 f 字符串:改进的字符串格式语法(指南)(还包括速度测试)
格式化字符串文字
字符串连接和格式化
在 Python 中拆分、连接和合并字符串
解决方案 6:
你写这个函数
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
解决方案 7:
如果要连接的字符串是文字,请使用字符串文字连接
re.compile(
"[A-Za-z_]" # letter or underscore
"[A-Za-z0-9_]*" # letter, digit or underscore
)
如果您想对字符串的一部分进行注释(如上所示)或者想要对部分文字(但不是全部)使用原始字符串或三重引号,这将很有用。
由于这发生在语法层,因此它使用零连接运算符。
解决方案 8:
正如 @jdi 提到的,Python 文档建议使用str.join
或io.StringIO
进行字符串连接。并表示开发人员应该预期+=
循环中的二次时间,即使自 Python 2.4 以来进行了优化。正如这个答案所说:
如果 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)) + '
'
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()
解决方案 9:
推荐的方法仍然是使用append和join。
解决方案 10:
虽然有些过时,但《像 Pythonista 一样编码:惯用的 Python》在本节中推荐了join()
。PythonSpeedPerformanceTips 在其字符串连接部分中也推荐了,并附有以下免责声明:+
本节的准确性在 Python 的后续版本中存在争议。在 CPython 2.5 中,字符串连接相当快,尽管这可能不适用于其他 Python 实现。有关讨论,请参阅 ConcatenationTestCode。
解决方案 11:
我的用例略有不同。我必须构建一个查询,其中有超过 20 个字段是动态的。我遵循了使用格式方法的这种方法
query = "insert into {0}({1},{2},{3}) values({4}, {5}, {6})"
query.format('users','name','age','dna','suzan',1010,'nda')
这对我来说比较简单,而不是使用 + 或其他方式
解决方案 12:
您也可以使用这个(更有效)。(https://softwareengineering.stackexchange.com/questions/304445/why-is-s-better-than-for-concatenation)
s += "%s" %(stringfromelsewhere)