separar - ¿Cómo agrego una cadena a otra en Python?
string en python (9)
Quiero una manera eficiente de agregar una cadena a otra en Python.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
¿Hay algún buen método incorporado para usar?
Básicamente, no hay diferencia. La única tendencia consistente es que Python parece estar disminuyendo su velocidad con cada versión ... :(
Lista
%%timeit
x = []
for i in range(100000000): # xrange on Python 2.7
x.append(''a'')
x = ''''.join(x)
Python 2.7
1 bucle, el mejor de 3: 7.34 s por bucle
Python 3.4
1 bucle, el mejor de 3: 7.99 s por bucle
Python 3.5
1 bucle, el mejor de 3: 8.48 s por bucle
Python 3.6
1 bucle, el mejor de 3: 9.93 s por bucle
Cuerda
%%timeit
x = ''''
for i in range(100000000): # xrange on Python 2.7
x += ''a''
Python 2.7 :
1 bucle, el mejor de 3: 7.41 s por bucle
Python 3.4
1 bucle, el mejor de 3: 9.08 s por bucle
Python 3.5
1 bucle, el mejor de 3: 8,82 s por bucle
Python 3.6
1 bucle, el mejor de 3: 9.24 s por bucle
No lo hagas
Es decir, para la mayoría de los casos es mejor que genere toda la cadena de una vez en lugar de agregarla a una cadena existente.
Por ejemplo, no hagas: obj1.name + ":" + str(obj1.count)
En su lugar: use "%s:%d" % (obj1.name, obj1.count)
Eso será más fácil de leer y más eficiente.
No optimices prematuramente Si no tiene motivos para creer que hay un cuello de botella de velocidad causado por las concatenaciones de cadenas, simplemente quédese con +
y +=
:
s = ''foo''
s += ''bar''
s += ''baz''
Dicho esto, si apuntas a algo como StringBuilder de Java, el lenguaje canónico de Python es agregar elementos a una lista y luego usar str.join
para concatenarlos al final:
l = []
l.append(''foo'')
l.append(''bar'')
l.append(''baz'')
s = ''''.join(l)
Realmente depende de su aplicación. Si recorres cientos de palabras y deseas .join()
a una lista, .join()
es mejor. Pero si estás juntando una oración larga, es mejor que uses +=
.
Si solo tiene una referencia a una cadena y concatena otra cadena hasta el final, CPython ahora hace un caso especial e intenta 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).
De 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=''''" "for i in xrange(10):s+=''a''" 1000000 loops, best of 3: 1.85 usec per loop $ python -m timeit -s"s=''''" "for i in xrange(100):s+=''a''" 10000 loops, best of 3: 16.8 usec per loop $ python -m timeit -s"s=''''" "for i in xrange(1000):s+=''a''" 10000 loops, best of 3: 158 usec per loop $ python -m timeit -s"s=''''" "for i in xrange(10000):s+=''a''" 1000 loops, best of 3: 1.71 msec per loop $ python -m timeit -s"s=''''" "for i in xrange(100000):s+=''a''" 10 loops, best of 3: 14.6 msec per loop $ python -m timeit -s"s=''''" "for i in xrange(1000000):s+=''a''" 10 loops, best of 3: 173 msec per loop
Sin embargo, es importante tener en cuenta que esta optimización no es parte de la especificación de Python. Es solo en la implementación de cPython que yo sepa. Las mismas pruebas empíricas en pypy o jython, por ejemplo, pueden mostrar el rendimiento anterior de O (n ** 2).
$ pypy -m timeit -s"s=''''" "for i in xrange(10):s+=''a''" 10000 loops, best of 3: 90.8 usec per loop $ pypy -m timeit -s"s=''''" "for i in xrange(100):s+=''a''" 1000 loops, best of 3: 896 usec per loop $ pypy -m timeit -s"s=''''" "for i in xrange(1000):s+=''a''" 100 loops, best of 3: 9.03 msec per loop $ pypy -m timeit -s"s=''''" "for i in xrange(10000):s+=''a''" 10 loops, best of 3: 89.5 msec per loop
Hasta aquí todo bien, pero entonces,
$ pypy -m timeit -s"s=''''" "for i in xrange(100000):s+=''a''" 10 loops, best of 3: 12.8 sec per loop
Ay aún peor que cuadrático. Entonces, pypy está haciendo algo que funciona bien con cadenas cortas, pero que funciona mal para cadenas más grandes.
añadir cadenas con la función __add__
str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)
Salida
Hello World
a=''foo''
b=''baaz''
a.__add__(b)
out: ''foobaaz''
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))
Eso une str1 y str2 con un espacio como separadores. También puede hacer "".join(str1, str2, ...)
. str.join()
toma un iterable, por lo que tendrías que poner las cadenas en una lista o una tupla.
Eso es tan eficiente como se puede hacer con un método incorporado.