TL; DR: Se você está apenas procurando a maneira mais simples de anexar strings, e não se importa com eficiência: #"foo" + "bar" + str(3)
187 Andrew #
Respostas:
609
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):
voidPyBytes_ConcatAndDel(registerPyObject**pv,registerPyObject*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){registerPyObject*v;registerPyBytesObject*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 */return0;}
É 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.
Interessante. Por "agora", você quer dizer Python 3.x?
Steve Tjoa
10
@ Steve, Não. É pelo menos em 2,6, talvez até 2,5 #
John La Rooy
8
Você citou a PyString_ConcatAndDelfunçã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
Winston Ewert
3
parabéns por explorar um recurso do CPython que fará o rastreamento do código em outras implementações. Mau conselho.
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)
Não sei quais são as implicações de velocidade de criar suas strings como listas e depois juntá-las (), mas acho que geralmente é a maneira mais limpa. Eu também tive grandes sucessos com o uso da notação% s em uma string para um mecanismo de modelagem de SQL que escrevi.
Richo
25
@ Richo Usar .join é mais eficiente. O motivo é que as seqüências de caracteres Python são imutáveis; portanto, usar repetidamente s + = more alocará muitas seqüências sucessivamente maiores. .join gerará a sequência final de uma só vez a partir de suas partes constituintes.
Ben
5
@ Ben, houve uma melhora significativa nessa área - veja minha resposta #
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.
Sinto muito não há nada mais fácil de ler do que (string + string) como o primeiro exemplo, o segundo exemplo pode ser mais eficiente, mas não mais legível
JqueryToAddNumbers
23
@ExceptionSlayer, string + string é bastante fácil de seguir. Mas "<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())
Winston Ewert
Isso não ajuda em nada quando o que estou tentando fazer é o equivalente aproximado de, digamos, "string. = Confirmdata ()" do PHP / perl ou similar.
Shadur
@ Shahadur, meu argumento é que você deve pensar novamente, realmente deseja fazer algo equivalente ou é melhor uma abordagem totalmente diferente?
Winston Ewert 23/02
1
E, neste caso, a resposta a essa pergunta é "Não, porque essa abordagem não cobrir meu caso de uso"
Shadur
11
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
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ê writeacrescenta texto a ele.
Se você está apenas acrescentando duas strings, use +.
isso realmente depende da sua aplicação. Se você está repetindo centenas de palavras e deseja anexá-las a uma lista, .join()é melhor. Mas se você estiver montando uma frase longa, é melhor usá-lo +=.
O código é bom, mas ajudaria a ter uma explicação que o acompanha. Por que usar esse método e não as outras respostas nesta página?
Cgmb
11
O uso 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)
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
costumava ser O (n ^ 2), mas agora é O (n).
Na fonte (bytesobject.c):
É fácil o suficiente para verificar empiricamente.
É 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.
Até aí tudo bem, mas então,
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.
fonte
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+=
: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:fonte
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.
fonte
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.
fonte
"<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:
Você pode fazer quase tudo dentro do aparelho
fonte
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
+
.fonte
isso realmente depende da sua aplicação. Se você está repetindo centenas de palavras e deseja anexá-las a uma lista,
.join()
é melhor. Mas se você estiver montando uma frase longa, é melhor usá-lo+=
.fonte
Basicamente, não há diferença. A única tendência consistente é que o Python parece estar ficando mais lento a cada versão ... :(
Lista
Python 2.7
Python 3.4
Python 3.5
Python 3.6
Corda
Python 2.7 :
Python 3.4
Python 3.5
Python 3.6
fonte
1.19 s
e,992 ms
respectivamente, em Python2.7anexar strings com a função __add__
Resultado
fonte
str + str2
ainda é mais curto.fonte
a.__add__(b)
é idêntico à escritaa+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.