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

新手上路,请多包涵
阅读 925
2 个回答

如果您只有一个对字符串的引用,并且您将另一个字符串连接到末尾,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'"
1000000 次循环,3 次中的最佳:每个循环 1.85 微秒
$ python -m timeit -s"s=''" "for i in xrange(100):s+='a'"
10000 次循环,3 次中的最佳:每个循环 16.8 微秒
$ python -m timeit -s"s=''" "for i in xrange(1000):s+='a'"
10000 个循环,3 个中最好的:每个循环 158 微秒
$ python -m timeit -s"s=''" "for i in xrange(10000):s+='a'"
1000 个循环,3 个循环中的最佳:每个循环 1.71 毫秒
$ python -m timeit -s"s=''" "for i in xrange(100000):s+='a'"
10 个循环,3 个循环中的最佳:每个循环 14.6 毫秒
$ python -m timeit -s"s=''" "for i in xrange(1000000):s+='a'"
10 个循环,3 个循环中的最佳:每个循环 173 毫秒

但重要的 是要注意这种优化不是 Python 规范的一部分。据我所知,它仅在 cPython 实现中。例如,对 pypy 或 jython 的相同经验测试可能会显示较旧的 O(n**2) 性能。

$ pypy -m timeit -s"s=''" "for i in xrange(10):s+='a'"
10000 个循环,最好的 3 个:每个循环 90.8 微秒
$ pypy -m timeit -s"s=''" "for i in xrange(100):s+='a'"
1000 个循环,最好的 3 个:每个循环 896 微秒
$ pypy -m timeit -s"s=''" "for i in xrange(1000):s+='a'"
100 个循环,3 个循环中的最佳:每个循环 9.03 毫秒
$ pypy -m timeit -s"s=''" "for i in xrange(10000):s+='a'"
10 个循环,3 个循环中的最佳:每个循环 89.5 毫秒

到目前为止一切顺利,但是,

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

哎哟甚至比二次更糟糕。所以 pypy 对短字符串效果很好,但对大字符串效果很差。

原文由 John La Rooy 发布,翻译遵循 CC BY-SA 3.0 许可协议

不要过早优化。如果您没有理由相信字符串连接会导致速度瓶颈,那么只需坚持使用 ++=

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

也就是说,如果您的目标是像 Java 的 StringBuilder 这样的东西,Python 的标准用法是将项目添加到列表中,然后使用 str.join 在最后将它们全部连接起来:

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

s = ''.join(l)

原文由 John Kugelman 发布,翻译遵循 CC BY-SA 2.5 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题