¿Cómo agrego una cadena a otra en Python?

594

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?

user469652
fuente
8
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):

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.

John La Rooy
fuente
14
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.
Jean-François Fabre
44
NO utilices esto. Pep8 declara explícitamente: el código debe escribirse de una manera que no perjudique a otras implementaciones de Python (PyPy, Jython, IronPython, Cython, Psyco, etc.)."".join(str_a, str_b)
Eraw
287

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)
John Kugelman
fuente
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
John La Rooy
41
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.

Rafe Kettler
fuente
¿Qué sucede si str1 es empy? ¿Se establecerá el espacio en blanco?
Jürgen K.
38

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.

Winston Ewert
fuente
54
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

print(f"1 + 1 == {1 + 1}")        # prints 1 + 1 == 2
Trenton
fuente
10

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 +.

Laurence Gonsalves
fuente
9

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 +=.

Ramy
fuente
5

Básicamente, no hay diferencia. La única tendencia consistente es que Python parece ser más lento 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, 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


Cuerda

%%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

Ostrokach
fuente
2
Supongo que depende. Tengo 1.19 sy 992 msrespectivamente en Python2.7
John La Rooy
5

agregar cadenas con la función __add__

str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)

Salida

Hello World
Sai Gopi N
fuente
44
str + str2Es aún más corto.
Nik O'Lai
2
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'
Rahul Shrivastava
fuente
1
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.
Addie