Quiero una manera eficiente de agregar una cadena a otra en Python, que no sea la siguiente.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
¿Hay algún buen método incorporado para usar?
Quiero una manera eficiente de agregar una cadena a otra en Python, que no sea la siguiente.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
¿Hay algún buen método incorporado para usar?
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
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):
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;
}
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.
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 +=
:
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.join
para concatenarlos todos al final:
l = []
l.append('foo')
l.append('bar')
l.append('baz')
s = ''.join(l)
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))
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.
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.
"<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())
Básicamente, no hay diferencia. La única tendencia consistente es que Python parece ser más lento con cada versión ... :(
%%timeit
x = []
for i in range(100000000): # xrange on Python 2.7
x.append('a')
x = ''.join(x)
Python 2.7
1 bucle, lo mejor de 3: 7.34 s por bucle
Python 3.4
1 ciclo, lo mejor de 3: 7.99 s por ciclo
Python 3.5
1 bucle, lo mejor de 3: 8.48 s por bucle
Python 3.6
1 ciclo, lo mejor de 3: 9.93 s por ciclo
%%timeit
x = ''
for i in range(100000000): # xrange on Python 2.7
x += 'a'
Python 2.7 :
1 bucle, mejor de 3: 7.41 s por bucle
Python 3.4
1 ciclo, mejor de 3: 9.08 s por ciclo
Python 3.5
1 ciclo, mejor de 3: 8.82 s por ciclo
Python 3.6
1 ciclo, lo mejor de 3: 9.24 s por ciclo
1.19 s
y 992 ms
respectivamente en Python2.7
agregar cadenas con la función __add__
str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)
Salida
Hello World
str + str2
Es aún más corto.
a='foo'
b='baaz'
a.__add__(b)
out: 'foobaaz'
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)