Eu quero uma maneira eficiente de acrescentar uma string a outra no Python, além do seguinte.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
Existe algum bom método interno para usar?
Eu quero uma maneira eficiente de acrescentar uma string a outra no Python, além do seguinte.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
Existe algum bom método interno para usar?
Respostas:
Se você tiver apenas uma referência a uma string e concatenar outra string até o final, o CPython agora fará casos especiais e tenta estender a string no lugar.
O resultado final é que a operação é amortizada O (n).
por exemplo
s = ""
for i in range(n):
s+=str(i)
costumava ser O (n ^ 2), mas agora é O (n).
Na fonte (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;
}
É fácil o suficiente para verificar empiricamente.
$ python -m timeit -s "s = ''" "para i no xrange (10): s + = 'a'" 1000000 loops, o melhor de 3: 1,85 usec por loop $ python -m timeit -s "s = ''" "para i no xrange (100): s + = 'a'" 10000 loops, o melhor de 3: 16,8 usec por loop $ python -m timeit -s "s = ''" "para i no xrange (1000): s + = 'a'" 10000 loops, o melhor de 3: 158 usec por loop $ python -m timeit -s "s = ''" "para i no xrange (10000): s + = 'a'" 1000 loops, o melhor de 3: 1,71 ms por loop $ python -m timeit -s "s = ''" "para i no xrange (100000): s + = 'a'" 10 loops, o melhor de 3: 14,6 ms por loop $ python -m timeit -s "s = ''" "para i no xrange (1000000): s + = 'a'" 10 loops, o melhor de 3: 173 ms por loop
É importante, no entanto, observar que essa otimização não faz parte das especificações do Python. É apenas na implementação cPython, tanto quanto eu sei. O mesmo teste empírico em pypy ou jython, por exemplo, pode mostrar o desempenho O (n ** 2) mais antigo.
$ pypy -m timeit -s "s = ''" "para i no xrange (10): s + = 'a'" 10000 loops, o melhor de 3: 90,8 usec por loop $ pypy -m timeit -s "s = ''" "para i no xrange (100): s + = 'a'" 1000 loops, o melhor de 3: 896 usec por loop $ pypy -m timeit -s "s = ''" "para i no xrange (1000): s + = 'a'" 100 loops, o melhor de 3: 9,03 ms por loop $ pypy -m timeit -s "s = ''" "para i no xrange (10000): s + = 'a'" 10 loops, o melhor de 3: 89,5 ms por loop
Até aí tudo bem, mas então,
$ pypy -m timeit -s "s = ''" "para i no xrange (100000): s + = 'a'" 10 loops, o melhor de 3: 12,8 seg por loop
ai ainda pior do que quadrático. Portanto, o pypy está fazendo algo que funciona bem com cadeias curtas, mas apresenta um desempenho ruim para cadeias maiores.
PyString_ConcatAndDel
função, mas incluiu o comentário para _PyString_Resize
. Além disso, o comentário realmente não estabelece sua reivindicação sobre o Big-O
"".join(str_a, str_b)
Não otimize prematuramente. Se você não tem motivos para acreditar que há um gargalo de velocidade causado por concatenações de strings, fique com +
e +=
:
s = 'foo'
s += 'bar'
s += 'baz'
Dito isto, se você está buscando algo como o StringBuilder do Java, o idioma canônico do Python é adicionar itens a uma lista e depois usá str.join
-los para concatená-los todos no final:
l = []
l.append('foo')
l.append('bar')
l.append('baz')
s = ''.join(l)
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))
Isso une str1 e str2 com um espaço como separadores. Você também pode fazer "".join(str1, str2, ...)
. str.join()
leva uma iterável, então você teria que colocar as strings em uma lista ou em uma tupla.
Isso é o mais eficiente possível para um método embutido.
Não.
Ou seja, na maioria dos casos, é melhor gerar a cadeia inteira de uma só vez, em vez de anexar a uma cadeia existente.
Por exemplo, não faça: obj1.name + ":" + str(obj1.count)
Em vez disso: use "%s:%d" % (obj1.name, obj1.count)
Isso será mais fácil de ler e mais eficiente.
"<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"
, acho menos legível e propenso a erros, em seguida,"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
O Python 3.6 nos fornece strings de f , que são uma delícia:
var1 = "foo"
var2 = "bar"
var3 = f"{var1}{var2}"
print(var3) # prints foobar
Você pode fazer quase tudo dentro do aparelho
print(f"1 + 1 == {1 + 1}") # prints 1 + 1 == 2
Se você precisar fazer muitas operações de acréscimo para criar uma cadeia grande, poderá usar o StringIO ou o cStringIO. A interface é como um arquivo. ou seja: você write
acrescenta texto a ele.
Se você está apenas acrescentando duas strings, use +
.
Basicamente, não há diferença. A única tendência consistente é que o Python parece estar ficando mais lento a cada versão ... :(
%%timeit
x = []
for i in range(100000000): # xrange on Python 2.7
x.append('a')
x = ''.join(x)
Python 2.7
1 loop, o melhor de 3: 7,34 s por loop
Python 3.4
1 loop, melhor de 3: 7.99 s por loop
Python 3.5
1 loop, o melhor de 3: 8,48 s por loop
Python 3.6
1 loop, melhor de 3: 9,93 s por loop
%%timeit
x = ''
for i in range(100000000): # xrange on Python 2.7
x += 'a'
Python 2.7 :
1 loop, o melhor de 3: 7,41 s por loop
Python 3.4
1 loop, o melhor de 3: 9,08 s por loop
Python 3.5
1 loop, o melhor de 3: 8,82 s por loop
Python 3.6
1 loop, o melhor de 3: 9,24 s por loop
1.19 s
e, 992 ms
respectivamente, em Python2.7
anexar strings com a função __add__
str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)
Resultado
Hello World
str + str2
ainda é mais curto.
a='foo'
b='baaz'
a.__add__(b)
out: 'foobaaz'
a.__add__(b)
é idêntico à escrita a+b
. Quando você concatena as strings usando o +
operador, o Python chama o __add__
método na string do lado esquerdo passando a string do lado direito como parâmetro.
"foo" + "bar" + str(3)