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

Max*_*Max 325 python string concat python-3.x

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

我可以这样写:

s += stringfromelsewhere
Run Code Online (Sandbox Code Playgroud)

或者像这样:

s = []
s.append(somestring)

later

s = ''.join(s)
Run Code Online (Sandbox Code Playgroud)

在写这个问题时,我发现了一篇很好的文章谈论这个话题.

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

但它是在Python 2.x.中,所以问题是在Python 3中做了哪些改变?

Len*_*bro 399

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

a = a + b:
0.11338996887207031
a += b:
0.11040496826171875
Run Code Online (Sandbox Code Playgroud)

但是,那些建议使用列表并附加到这些列表然后加入这些列表的人这样做是因为将字符串附加到列表可能会比扩展字符串快得多.在某些情况下,这可能是真的.例如,这里是一个字符串的一百万个附加,首先是一个字符串,然后是一个列表:

a += b:
0.10780501365661621
a.append(b):
0.1123361587524414
Run Code Online (Sandbox Code Playgroud)

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

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

a += b:
0.41823482513427734
a.append(b):
0.010656118392944336
Run Code Online (Sandbox Code Playgroud)

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

a.join(a):
0.43739795684814453
Run Code Online (Sandbox Code Playgroud)

Oups.即使在这种情况下,附加/加入也会变慢.

那么这个推荐来自哪里?Python 2?

a += b:
0.165287017822
a.append(b):
0.0132720470428
a.join(a):
0.114929914474
Run Code Online (Sandbox Code Playgroud)

好吧,如果你使用非常长的字符串(你通常不使用,你会在内存中使用100MB的字符串,那么append/join会稍微快一点)

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

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

(更新:原来,当我更仔细,使用做了测试++=比较快的两个字符串关于Python 2.3,以及推荐使用.''.join()必须是一个误会)

但是,这是CPython.其他实现可能有其他问题.这也是为什么过早优化是万恶之源的另一个原因.除非你先测量它,否则不要使用"更快"的技术.

因此,进行字符串连接的"最佳"版本是使用+或+ =.如果这对你来说变得缓慢,这是不太可能的,那么做一些其他事情.

那么为什么我在代码中使用了大量的append/join呢?因为有时它实际上更清晰.特别是当你应该连接在一起时,应该用空格或逗号或换行符分隔.

  • 为什么这么受欢迎呢?如何更好地使用仅对一个特定实现有效的算法,并且基本上等于修复二次时间算法的脆弱黑客?你也完全误解了"过早优化是万恶之源"的观点.该报价正在讨论SMALL优化.这是从O(n ^ 2)到O(n),这不是一个小优化. (14认同)
  • 如果你有多个字符串(n> 10)"".join(list_of_strings)仍然更快 (10认同)
  • + =很快的原因是,如果refcount是1,那么cpython中存在性能破解 - 几乎所有其他python实现都会崩溃(除了相当特殊的配置pypy构建) (10认同)
  • 这是实际的引用:"我们应该忘记效率低,大约97%的时间说:过早的优化是所有邪恶的根源.然而,我们不应该把关键的3%的机会放弃.一个优秀的程序员不会通过这样的推理使自己变得自满,他会明智地仔细查看关键代码;但只有在识别出代码之后才能" (10认同)
  • 没有人说a + b慢。当您多次执行a = a + b时,这是二次方的。a + b + c并不慢,我重复_not slow_,因为它只需要遍历每个字符串一次,而它必须使用a = a + b方法重新遍历先前的字符串很多次(假定处于循环中)某种)。请记住,字符串是不可变的。 (2认同)

jdi*_*jdi 46

如果你连接很多值,那么两者都没有.附加清单很昂贵.您可以使用StringIO.特别是如果你在很多操作中构建它.

from cStringIO import StringIO
# python3:  from io import StringIO

buf = StringIO()

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

buf.getvalue()
# 'foofoofoo'
Run Code Online (Sandbox Code Playgroud)

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

来自python FAQ:将多个字符串连接在一起的最有效方法是什么?

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

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

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)
Run Code Online (Sandbox Code Playgroud)

(另一个合理有效的习惯是使用io.StringIO)

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

result = bytearray()
for b in my_bytes_objects:
    result += b
Run Code Online (Sandbox Code Playgroud)

编辑:我是愚蠢的,并将结果向后粘贴,使其看起来像是附加到列表比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
Run Code Online (Sandbox Code Playgroud)

  • Py3中不存在`cStringIO`.请改用"io.StringIO". (2认同)
  • 至于为什么反复追加一个字符串可能很昂贵:http://www.joelonsoftware.com/articles/fog0000000319.html (2认同)

Sup*_*ova 24

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

  • 如果 `f'{a}{b}'` 不比 `a += b` 或 `a + b` _更高效_,我不明白这对专门询问性能的问题有何有意义的响应。此功能是语法糖(当然是好的且有用的糖!),而不是性能优化。 (5认同)

MRA*_*RAB 8

推荐的方法仍然是使用append和join.


dro*_*oid 8

如果要连接的字符串是文字,请使用字符串文字串联

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

如果您想要对字符串的一部分进行注释(如上所述),或者您希望对文字的一部分使用原始字符串或三重引号但不是全部,则此选项非常有用.

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


Lev*_*von 6

虽然有些过时,代码像Pythonista:地道的Python建议join()+ 本节.与PythonSpeedPerformanceTips字符串连接一节中一样,使用以下免责声明:

对于更高版本的Python,本节的准确性存在争议.在CPython 2.5中,字符串连接相当快,尽管这可能不适用于其他Python实现.有关讨论,请参见ConcatenationTestCode.


bad*_*cks 6

使用'+'进行字符串连接是稳定性和交叉实现方面最糟糕的连接方法,因为它不支持所有值.PEP8标准不鼓励这种做法,并鼓励使用format(),join()和append()进行长期使用.

  • 参考链接本来不错:) (5认同)
  • 多么荒谬的情况啊。这是人们首先被教导如何做的事情之一,而我们在象牙塔里的巫师们发布了 PEP 来阻止它,因为它很脆弱。 (5认同)

Sha*_*eem 6

你写这个函数

def str_join(*args):
    return ''.join(map(str, args))
Run Code Online (Sandbox Code Playgroud)

然后,您可以随时随地致电

str_join('Pine')  # Returns : Pine
str_join('Pine', 'apple')  # Returns : Pineapple
str_join('Pine', 'apple', 3)  # Returns : Pineapple3
Run Code Online (Sandbox Code Playgroud)


Kus*_*era 6

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

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

我通过以下文章创建了这个小总结。


saa*_*aaj 5

如@jdi所述,Python文档建议使用str.joinio.StringIO进行字符串连接。并说开发人员应该从+=循环中期待二次时间,即使自Python 2.4开始进行了优化。正如这个答案所说:

如果Python检测到left参数没有其他引用,它将调用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
Run Code Online (Sandbox Code Playgroud)

由于二次时间的复杂性,该代码可能在文学上运行数小时。以下是具有建议的数据结构的替代方案:

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

还有一个微基准测试:

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

微观基准


归档时间:

查看次数:

622542 次

最近记录:

6 年,4 月 前