Answers:
如果只有一个对字符串的引用,并且将另一个字符串连接到末尾,则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 =”“”对于xrange(10):s + ='a' 1000000次循环,每循环3:1.85最佳 $ python -m timeit -s“ s =”“”对于xrange(100):s + ='a' 10000次循环,最佳为3次:每个循环16.8微秒 $ python -m timeit -s“ s =”“”对于xrange(1000)中的我来说:s + ='a'“ 10000次循环,最佳为3次:每个循环158微秒 $ python -m timeit -s“ s =”“”对于xrange(10000):s + ='a' 1000次循环,每循环3:1.71毫秒最佳 $ python -m timeit -s“ s =”“”对于xrange(100000):s + ='a' 10个循环,每循环最好3:14.6毫秒 $ python -m timeit -s“ s =”“”对于xrange(1000000):s + ='a' 10个循环,最佳3:每个循环173毫秒
不过,请务必注意,此优化不是Python规范的一部分。据我所知,它仅在cPython实现中。例如,对pypy或jython进行的相同经验测试可能会显示较旧的O(n ** 2)性能。
$ pypy -m timeit -s“ s =”“”对于xrange(10)中的i:s + ='a'“ 10000次循环,最好为3:每个循环90.8微秒 $ pypy -m timeit -s“ s =”“”对于xrange(100)中的i:s + ='a'“ 1000个循环,每循环3:896最佳 $ pypy -m timeit -s“ s =”“”对于xrange(1000)中的i:s + ='a'“ 100个循环,每个循环最好3:9.03毫秒 $ pypy -m timeit -s“ s =”“”对于xrange(10000):s + ='a' 10个循环,最好为3:每个循环89.5毫秒
到目前为止一切顺利,但随后,
$ pypy -m timeit -s“ s =”“”对于xrange(100000):s + ='a' 10次循环,每循环3:12.8秒的最佳时间
哎呀,甚至比二次还差。因此,pypy可以在短字符串上做得很好,但是在较大的字符串上却表现不佳。
PyString_ConcatAndDel
函数,但包含的注释_PyString_Resize
。此外,意见并没有真正建立你的关于大O要求
"".join(str_a, str_b)
不要过早优化。如果您没有理由相信字符串连接会造成速度瓶颈,那么请坚持使用+
and +=
:
s = 'foo'
s += 'bar'
s += 'baz'
就是说,如果您的目标是Java的StringBuilder之类的东西,那么规范的Python习惯用法就是将项目添加到列表中,然后最后str.join
将它们全部串联起来:
l = []
l.append('foo')
l.append('bar')
l.append('baz')
s = ''.join(l)
别。
也就是说,在大多数情况下,最好一次性生成整个字符串,而不是附加到现有字符串。
例如,不要: obj1.name + ":" + str(obj1.count)
相反:使用 "%s:%d" % (obj1.name, obj1.count)
这将更容易阅读和更有效。
"<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())
如果需要执行许多附加操作来构建大字符串,则可以使用StringIO或cStringIO。界面就像一个文件。即:您write
在其上附加文本。
如果您只是追加两个字符串,请使用+
。
基本上没有区别。唯一一致的趋势是,每个版本的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 s 最佳
Python 3.4
1个循环,每个循环最好3:7.99 s
Python 3.5
1次循环,每循环3:8.48 s 最佳
Python 3.6
1次循环,每循环3:9.93 s 最佳
%%timeit
x = ''
for i in range(100000000): # xrange on Python 2.7
x += 'a'
Python 2.7:
1次循环,每循环3:7.41 s最佳
Python 3.4
1个循环,每个循环最好3:9.08 s
Python 3.5
1次循环,每循环3:8.82 s 最佳
Python 3.6
1次循环,每循环3:9.24 s 最佳
1.19 s
992 ms
"foo" + "bar" + str(3)