답변:
문자열에 대한 참조가 하나만 있고 다른 문자열을 끝에 연결하는 경우 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)의 i의 경우 : s + = 'a'" 1000000 루프, 최고 3 : 1.85 루프 당 루프 $ python -m timeit -s "s = ''" "xrange (100)의 i의 경우 : s + = 'a'" 루프 당 10000 개의 루프 중 3 : 16.8 usec $ python -m timeit -s "s = ''" "xrange (1000)에서 i의 경우 : s + = 'a'" 루프 당 10000 개의 루프, 최고 3 : 158 usec $ python -m timeit -s "s = ''" "xrange (10000)의 i의 경우 : s + = 'a'" 1000 루프, 루프 당 3 : 1.71 msec 최고 $ python -m timeit -s "s = ''" "xrange (100000)의 i의 경우 : s + = 'a'" 루프 당 3 개의 루프 : 14.6msec $ python -m timeit -s "s = ''" "xrange (1000000) : s + = 'a'"의 i 10 개 루프, 루프 당 3 : 173msec 최고
그것은 중요 이 최적화 파이썬 사양의 일부가 아니라는 것을 참고하지만. 내가 아는 한 cPython 구현에만 있습니다. 예를 들어 pypy 또는 jython에 대한 동일한 경험적 테스트는 이전 O (n ** 2) 성능을 보여줄 수 있습니다.
$ pypy -m timeit -s "s = ''" "xrange (10)의 i의 경우 : s + = 'a'" 루프 당 10000 개의 루프, 최고 3 : 90.8 usec $ pypy -m timeit -s "s = ''" "xrange (100)의 i의 경우 : s + = 'a'" 루프 당 1000 루프, 3 : 3 : 896 usec 이상 $ pypy -m timeit -s "s = ''" "xrange (1000)의 i의 경우 : s + = 'a'" 루프 당 100 개, 3 개 최고 : 9.03msec $ pypy -m timeit -s "s = ''" "xrange (10000)의 i의 경우 : s + = 'a'" 10 개 루프, 3 개 중 최고 : 89.5msec (루프 당)
지금까지는 좋지만
$ pypy -m timeit -s "s = ''" "xrange (100000)의 i의 경우 : s + = 'a'" 루프 당 10 개, 최고 3 : 12.8 초
이차보다 더 나쁘다. 따라서 pypy는 짧은 문자열에서는 잘 작동하지만 더 큰 문자열에서는 성능이 좋지 않습니다.
PyString_ConcatAndDel
함수를 인용 했지만에 대한 주석을 포함했습니다 _PyString_Resize
. 또한,이 의견은 Big-O
"".join(str_a, str_b)
조기 최적화하지 마십시오. 문자열 연결로 인한 속도 병목 현상이 있다고 생각할 이유가 없다면 +
and 만 사용하십시오 +=
.
s = 'foo'
s += 'bar'
s += 'baz'
즉, Java의 StringBuilder와 같은 것을 목표로하는 경우 표준 파이썬 관용구는 항목을 목록에 추가 한 다음 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())
파이썬 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
기본적으로 차이는 없습니다. 유일한 일관된 추세는 파이썬이 모든 버전에서 느려지고있는 것 같습니다 ... :(
%%timeit
x = []
for i in range(100000000): # xrange on Python 2.7
x.append('a')
x = ''.join(x)
파이썬 2.7
1 루프, 루프 당 3:34 초
파이썬 3.4
1 개 루프, 3 최고 : 7.99 루프 당의
파이썬 3.5
1 루프, 루프 당 최고 3 : 8.48 초
파이썬 3.6
1 루프, 루프 당 3 : 9.93 초
%%timeit
x = ''
for i in range(100000000): # xrange on Python 2.7
x += 'a'
파이썬 2.7 :
1 루프, 루프 당 최고 3 : 3.1 초
파이썬 3.4
1 루프, 루프 당 최고 3 : 9.08 초
파이썬 3.5
1 개 루프, 3 최고 : 8.82 루프 당의
파이썬 3.6
1 개 루프, 3 최고 : 9.24 루프 당의
1.19 s
하고 992 ms
Python2.7에 각각
"foo" + "bar" + str(3)