TL; DR: si solo está buscando la manera simple de agregar cadenas, y no le importa la eficiencia:"foo" + "bar" + str(3)
Andrew
Respuestas:
609
Si solo tiene una referencia a una cadena y concatena otra cadena hasta el final, CPython ahora aplica casos especiales y trata de extender la cadena en su lugar.
El resultado final es que la operación se amortiza O (n).
p.ej
s =""for i in range(n):
s+=str(i)
solía ser O (n ^ 2), pero ahora es O (n).
Desde la fuente (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;}
Es bastante fácil de verificar empíricamente.
$ python -m timeit -s "s = ''" "para i en xrange (10): s + = 'a'"
1000000 bucles, lo mejor de 3: 1.85 usec por bucle
$ python -m timeit -s "s = ''" "para i en xrange (100): s + = 'a'"
10000 bucles, lo mejor de 3: 16,8 usec por bucle
$ python -m timeit -s "s = ''" "para i en xrange (1000): s + = 'a'"
10000 bucles, lo mejor de 3: 158 usec por bucle
$ python -m timeit -s "s = ''" "para i en xrange (10000): s + = 'a'"
1000 bucles, lo mejor de 3: 1.71 ms por bucle
$ python -m timeit -s "s = ''" "para i en xrange (100000): s + = 'a'"
10 bucles, lo mejor de 3: 14,6 ms por bucle
$ python -m timeit -s "s = ''" "para i en xrange (1000000): s + = 'a'"
10 bucles, lo mejor de 3: 173 ms por bucle
Sin embargo, es importante tener en cuenta que esta optimización no es parte de la especificación de Python. Hasta donde yo sé, solo está en la implementación de cPython. La misma prueba empírica en pypy o jython, por ejemplo, podría mostrar el rendimiento anterior de O (n ** 2).
$ pypy -m timeit -s "s = ''" "para i en xrange (10): s + = 'a'"
10000 bucles, lo mejor de 3: 90.8 usec por bucle
$ pypy -m timeit -s "s = ''" "para i en xrange (100): s + = 'a'"
1000 bucles, lo mejor de 3: 896 usec por bucle
$ pypy -m timeit -s "s = ''" "para i en xrange (1000): s + = 'a'"
100 bucles, lo mejor de 3: 9.03 ms por bucle
$ pypy -m timeit -s "s = ''" "para i en xrange (10000): s + = 'a'"
10 bucles, lo mejor de 3: 89.5 ms por bucle
Hasta ahora todo bien, pero entonces,
$ pypy -m timeit -s "s = ''" "para i en xrange (100000): s + = 'a'"
10 bucles, lo mejor de 3: 12.8 segundos por bucle
ouch incluso peor que cuadrático. Entonces pypy está haciendo algo que funciona bien con cadenas cortas, pero funciona mal para cadenas más grandes.
Interesante. Por "ahora", ¿te refieres a Python 3.x?
Steve Tjoa
10
@Steve, No. Es al menos en 2.6 quizás incluso 2.5
John La Rooy
8
Has citado la PyString_ConcatAndDelfunción pero has incluido el comentario _PyString_Resize. Además, el comentario realmente no establece su reclamo con respecto al Big-O
Winston Ewert
3
felicidades por explotar una característica de CPython que hará que el código se rastree en otras implementaciones. Mal consejo.
No optimices prematuramente. Si no tiene ninguna razón para creer que hay un cuello de botella de velocidad causado por concatenaciones de cuerdas, entonces quédese con +y +=:
s ='foo'
s +='bar'
s +='baz'
Dicho esto, si estás buscando algo como StringBuilder de Java, el modismo canónico de Python es agregar elementos a una lista y luego usarlos str.joinpara concatenarlos todos al final:
l =[]
l.append('foo')
l.append('bar')
l.append('baz')
s =''.join(l)
No sé cuáles son las implicaciones de velocidad de construir sus cadenas como listas y luego .join () ing, pero creo que generalmente es la forma más limpia. También he tenido grandes éxitos al usar la notación% s dentro de una cadena para un motor de plantillas SQL que escribí.
richo
25
@Richo Usar .join es más eficiente. La razón es que las cadenas de Python son inmutables, por lo que usar repetidamente s + = more asignará muchas cadenas sucesivamente más grandes. .join generará la cadena final de una vez desde sus partes constituyentes.
Ben
55
@Ben, ha habido una mejora significativa en esta área - vea mi respuesta
Eso une str1 y str2 con un espacio como separadores. También puedes hacer "".join(str1, str2, ...). str.join()toma un iterable, por lo que tendría que poner las cadenas en una lista o una tupla.
Eso es lo más eficiente posible para un método integrado.
lo siento, no hay nada más fácil de leer que (cadena + cadena) como el primer ejemplo, el segundo ejemplo podría ser más eficiente, pero no más legible
JqueryToAddNumbers
23
@ExceptionSlayer, string + string es bastante fácil de seguir. Pero "<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>", me parece menos legible y propenso a errores entonces"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Winston Ewert
Esto no ayuda en absoluto cuando lo que intento hacer es el equivalente aproximado de, por ejemplo, "string. = Verifieddata ()" de PHP / perl o similar.
Shadur
@Shadur, mi punto es que deberías pensar de nuevo, ¿realmente quieres hacer algo equivalente, o es mejor un enfoque completamente diferente?
Winston Ewert
1
Y en este caso la respuesta a esa pregunta es "No, porque ese enfoque no cubre mi caso de uso"
Shadur
11
Python 3.6 nos da cadenas f , que son una delicia:
var1 ="foo"
var2 ="bar"
var3 = f"{var1}{var2}"print(var3)# prints foobar
Puedes hacer casi cualquier cosa dentro de las llaves
Si necesita realizar muchas operaciones de adición para crear una cadena grande, puede usar StringIO o cStringIO. La interfaz es como un archivo. es decir: writeanexarle texto.
Si solo está agregando dos cadenas, simplemente use +.
Realmente depende de su aplicación. Si está recorriendo cientos de palabras y desea agregarlas todas a una lista, .join()es mejor. Pero si estás armando una oración larga, es mejor que la uses +=.
El código es bueno, pero ayudaría tener una explicación que lo acompañe. ¿Por qué usar este método en lugar de las otras respuestas en esta página?
cgmb
11
Usar a.__add__(b)es idéntico a escribir a+b. Cuando concatena cadenas utilizando el +operador, Python llamará al __add__método en la cadena del lado izquierdo pasando la cadena del lado derecho como parámetro.
"foo" + "bar" + str(3)
Respuestas:
Si solo tiene una referencia a una cadena y concatena otra cadena hasta el final, CPython ahora aplica casos especiales y trata de extender la cadena en su lugar.
El resultado final es que la operación se amortiza O (n).
p.ej
solía ser O (n ^ 2), pero ahora es O (n).
Desde la fuente (bytesobject.c):
Es bastante fácil de verificar empíricamente.
Sin embargo, es importante tener en cuenta que esta optimización no es parte de la especificación de Python. Hasta donde yo sé, solo está en la implementación de cPython. La misma prueba empírica en pypy o jython, por ejemplo, podría mostrar el rendimiento anterior de O (n ** 2).
Hasta ahora todo bien, pero entonces,
ouch incluso peor que cuadrático. Entonces pypy está haciendo algo que funciona bien con cadenas cortas, pero funciona mal para cadenas más grandes.
fuente
PyString_ConcatAndDel
función pero has incluido el comentario_PyString_Resize
. Además, el comentario realmente no establece su reclamo con respecto al Big-O"".join(str_a, str_b)
No optimices prematuramente. Si no tiene ninguna razón para creer que hay un cuello de botella de velocidad causado por concatenaciones de cuerdas, entonces quédese con
+
y+=
:Dicho esto, si estás buscando algo como StringBuilder de Java, el modismo canónico de Python es agregar elementos a una lista y luego usarlos
str.join
para concatenarlos todos al final:fuente
Eso une str1 y str2 con un espacio como separadores. También puedes hacer
"".join(str1, str2, ...)
.str.join()
toma un iterable, por lo que tendría que poner las cadenas en una lista o una tupla.Eso es lo más eficiente posible para un método integrado.
fuente
No lo hagas
Es decir, para la mayoría de los casos es mejor generar la cadena completa de una vez en lugar de agregarla a una cadena existente.
Por ejemplo, no hagas:
obj1.name + ":" + str(obj1.count)
En cambio: use
"%s:%d" % (obj1.name, obj1.count)
Será más fácil de leer y más eficiente.
fuente
"<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"
, me parece menos legible y propenso a errores entonces"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Python 3.6 nos da cadenas f , que son una delicia:
Puedes hacer casi cualquier cosa dentro de las llaves
fuente
Si necesita realizar muchas operaciones de adición para crear una cadena grande, puede usar StringIO o cStringIO. La interfaz es como un archivo. es decir:
write
anexarle texto.Si solo está agregando dos cadenas, simplemente use
+
.fuente
Realmente depende de su aplicación. Si está recorriendo cientos de palabras y desea agregarlas todas a una lista,
.join()
es mejor. Pero si estás armando una oración larga, es mejor que la uses+=
.fuente
Básicamente, no hay diferencia. La única tendencia consistente es que Python parece ser más lento con cada versión ... :(
Lista
Python 2.7
Python 3.4
Python 3.5
Python 3.6
Cuerda
Python 2.7 :
Python 3.4
Python 3.5
Python 3.6
fuente
1.19 s
y992 ms
respectivamente en Python2.7agregar cadenas con la función __add__
Salida
fuente
str + str2
Es aún más corto.fuente
a.__add__(b)
es idéntico a escribira+b
. Cuando concatena cadenas utilizando el+
operador, Python llamará al__add__
método en la cadena del lado izquierdo pasando la cadena del lado derecho como parámetro.