如何在Python中将一个字符串附加到另一个字符串?

746

17
如果你只是想简单地连接字符串,并且不关心效率:可以使用 "foo" + "bar" + str(3) - Andrew
13个回答

762
如果你只有一个字符串引用并且将另一个字符串连接到末尾,CPython现在会特殊处理并尝试原地扩展字符串。
最终结果是该操作的摊销复杂度为O(n)。
例如:
s = ""
for i in range(n):
    s += str(i)

曾经是O(n^2),但现在是O(n)。

更多信息

来自源代码(bytesobject.c):

void
PyBytes_ConcatAndDel(register PyObject **pv, register PyObject *w)
{
    PyBytes_Concat(pv, w);
    Py_XDECREF(w);
}


/* The following function breaks the notion that strings are immutable:
   it changes the size of a string.  We get away with this only if there
   is only one module referencing the object.  You can also think of it
   as creating a new string object and destroying the old one, only
   more efficiently.  In any case, don't use this if the string may
   already be known to some other part of the code...
   Note that if there's not enough memory to resize the string, the original
   string object at *pv is deallocated, *pv is set to NULL, an "out of
   memory" exception is set, and -1 is returned.  Else (on success) 0 is
   returned, and the value in *pv may or may not be the same as on input.
   As always, an extra byte is allocated for a trailing \0 byte (newsize
   does *not* include that), and a trailing \0 byte is stored.
*/

int
_PyBytes_Resize(PyObject **pv, Py_ssize_t newsize)
{
    register PyObject *v;
    register PyBytesObject *sv;
    v = *pv;
    if (!PyBytes_Check(v) || Py_REFCNT(v) != 1 || newsize < 0) {
        *pv = 0;
        Py_DECREF(v);
        PyErr_BadInternalCall();
        return -1;
    }
    /* XXX UNREF/NEWREF interface should be more symmetrical */
    _Py_DEC_REFTOTAL;
    _Py_ForgetReference(v);
    *pv = (PyObject *)
        PyObject_REALLOC((char *)v, PyBytesObject_SIZE + newsize);
    if (*pv == NULL) {
        PyObject_Del(v);
        PyErr_NoMemory();
        return -1;
    }
    _Py_NewReference(*pv);
    sv = (PyBytesObject *) *pv;
    Py_SIZE(sv) = newsize;
    sv->ob_sval[newsize] = '\0';
    sv->ob_shash = -1;          /* invalidate cached hash value */
    return 0;
}

很容易通过实证来验证。
$ python -m timeit -s"s=''" "for i in xrange(10):s+='a'"
每个循环1.85微秒,3次取最佳值
$ python -m timeit -s"s=''" "for i in xrange(100):s+='a'"
每个循环16.8微秒,3次取最佳值
$ python -m timeit -s"s=''" "for i in xrange(1000):s+='a'"
每个循环158微秒,3次取最佳值
$ python -m timeit -s"s=''" "for i in xrange(10000):s+='a'"
每个循环1.71毫秒,3次取最佳值
$ python -m timeit -s"s=''" "for i in xrange(100000):s+='a'"
每个循环14.6毫秒,3次取最佳值
$ python -m timeit -s"s=''" "for i in xrange(1000000):s+='a'"
每个循环173毫秒,3次取最佳值
但是重要的是需要注意这种优化不是Python规范的一部分。据我所知,它只在cPython实现中存在。例如,在pypy或jython上进行相同的经验测试可能会显示旧的O(n ** 2)性能。
$ pypy -m timeit -s"s=''" "for i in xrange(10):s+='a'"
每个循环90.8微秒,3次取最佳值
$ pypy -m timeit -s"s=''" "for i in xrange(100):s+='a'"
每个循环896微秒,3次取最佳值
$ pypy -m timeit -s"s=''" "for i in xrange(1000):s+='a'"
每个循环9.03毫秒,3次取最佳值
$ pypy -m timeit -s"s=''" "for i in xrange(10000):s+='a'"
每个循环89.5毫秒,3次取最佳值

到目前为止还不错,但是,

$ pypy -m timeit -s"s=''" "for i in xrange(100000):s+='a'"
每个循环12.8秒,3次取最佳值

哎呀,甚至比二次更糟。因此,对于短字符串,pypy正在执行某些有效的操作,但是对于较长的字符串性能较差。


17
有意思。您所说的“现在”,是指 Python 3.x 吗? - Steve Tjoa
13
@Steve,不是。至少在2.6版本中,甚至可能还有2.5版本。 - John La Rooy
8
你引用了PyString_ConcatAndDel函数,但是却包含了_PyString_Resize的注释。此外,该注释并没有真正证明你关于Big-O的主张。 - Winston Ewert
5
祝贺您利用了CPython的功能,但这将使代码在其他实现上运行缓慢。不是好的建议。 - Jean-François Fabre
20
不要使用这个。Pep8明确规定:代码应该以不会对其他Python实现(如PyPy,Jython,IronPython,Cython,Psyco等)造成不利影响的方式编写。它还给出了一个需要避免的具体示例,因为它太易受影响。更好的方法是使用"".join(str_a, str_b) - Er...
显示剩余7条评论

363

不要过早优化。如果你没有理由相信字符串连接会成为速度瓶颈,那就继续使用++=


s  = 'foo'
s += 'bar'
s += 'baz'

话虽如此,但如果你想要类似于Java的StringBuilder的功能,Python中的典型做法是将元素添加到列表中,然后使用str.join方法在最后将它们连接起来:

l = []
l.append('foo')
l.append('bar')
l.append('baz')

s = ''.join(l)

我不知道将字符串构建为列表,然后使用.join()的速度影响如何,但我发现这通常是最清晰的方法。我还尝试过在字符串中使用%s符号作为我编写的SQL模板引擎的一种方式,并取得了巨大的成功。 - richo
40
使用 .join 更高效。原因是 Python 字符串是不可变的,因此反复使用 s += more 会分配许多连续更大的字符串。.join 将从其组成部分一次性生成最终字符串。 - Ben
7
@Ben,这方面已有显著改进 - 请看我的回答。 - John La Rooy

63
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))

这个方法使用空格作为分隔符连接str1和str2。你也可以使用"".join(str1, str2, ...)str.join()接受一个可迭代对象,因此你需要将字符串放入列表或元组中。

对于内置方法来说,这已经是效率最高的了。


1
如果str1为空,会发生什么?空格会被设置吗? - Jürgen K.
@JürgenK。是的。它不会区别对待空字符串。它只是将所有字符串放在一起,并在它们之间放置分隔符。 - xuiqzy

39

不要这样做。

通常情况下,最好一次性生成整个字符串,而不是将内容附加到现有字符串中。

例如,不要这样写:obj1.name + ":" + str(obj1.count)

相反地,可以使用 "%s:%d" % (obj1.name, obj1.count) 来代替。

这样更易于阅读和更有效率。


72
很抱歉,没有比(字符串 + 字符串)这个第一个例子更易读的东西了,第二个例子可能更高效,但不够易读。 - JqueryToAddNumbers
26
@ExceptionSlayer,string+string很容易理解。但是"<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"的可读性不高且容易出错,相比之下"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())更易读且更健壮。 - Winston Ewert
1
这并没有什么帮助,因为我正在尝试做的大致相当于PHP/Perl的"string .= verifydata()"或类似操作。 - Shadur
2
在这种情况下,对于那个问题的答案是“不行,因为那种方法无法涵盖我的使用场景”。 - Shadur
3
在Python 3.6中,我们有 f"<div class='{class_name}' id='{generateUniqueId()}'>{message_text}</div>" - Trenton
显示剩余3条评论

32

Python 3.6给我们带来了f-strings,这是一种令人愉悦的语言特性:

var1 = "foo"
var2 = "bar"
var3 = f"{var1}{var2}"
print(var3)                       # prints foobar

在花括号内,您可以做几乎任何事情。

print(f"1 + 1 == {1 + 1}")        # prints 1 + 1 == 2

16
如果您需要执行许多附加操作以构建大字符串,则可以使用StringIO或cStringIO。接口类似于文件。即:您可以使用write将文本附加到其中。
如果只是附加两个字符串,那么只需使用+

10

这实际上取决于您的应用程序。如果您正在循环遍历数百个单词并希望将它们全部追加到列表中,.join() 更好。但如果您正在组成一个长句子,最好使用 +=


7

基本上没有什么区别。唯一的一致趋势是Python似乎在每个版本中都变得越来越慢了... :(


列表

%%timeit
x = []
for i in range(100000000):  # xrange on Python 2.7
    x.append('a')
x = ''.join(x)

Python 2.7

1次循环,3次中的最佳结果: 每次循环需要7.34

Python 3.4

1次循环,3次中的最佳结果: 每次循环需要7.99

Python 3.5

1次循环,3次中的最佳结果: 每次循环需要8.48

Python 3.6

1次循环,3次中的最佳结果: 每次循环需要9.93


字符串

%%timeit
x = ''
for i in range(100000000):  # xrange on Python 2.7
    x += 'a'

Python 2.7:

循环1次,3次中最佳结果:7.41秒每次循环

Python 3.4

循环1次,3次中最佳结果:9.08秒每次循环

Python 3.5

循环1次,3次中最佳结果:8.82秒每次循环

Python 3.6

循环1次,3次中最佳结果:9.24秒每次循环


3
我猜这要看情况。在Python2.7中,我分别得到了1.19秒992毫秒的结果。 - John La Rooy

5

使用add函数连接字符串:

str1 = "Hello"
str2 = " World"
str3 = str1.__add__(str2)
print(str3)

输出:

Hello World

10
str + str2 仍然更短。 - Nik O'Lai

3
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'

2
代码很好,但最好有相应的解释。为什么要使用这种方法而不是页面上的其他答案? - cgmb
17
使用a.__add__(b)与写a+b完全相同。当使用+运算符连接字符串时,Python会调用左侧字符串的__add__方法,并将右侧字符串作为参数传递。 - Addie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接