¿Cómo puedo añadir una cadena a otra en Python?


Quiero una forma eficiente de anexar una cadena a otra en Python.

var1 = "foo"
var2 = "bar"
var3 = var1 + var2

¿ Hay algún buen método incorporado para usar?

Author: Steven Vascellaro, 2010-12-14

9 answers

Si solo tiene una referencia a una cadena y concatena otra cadena al final, CPython ahora aplica este caso especial e intenta extender la cadena en su lugar.

El resultado final es que la operación se amortiza O(n).

Por ejemplo

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=''" "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 sólo en la implementación de CPython que yo sepa. La misma prueba empírica en pypy o jython, por ejemplo, podría mostrar el rendimiento de O(n**2) más antiguo .

$ 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 ahora 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

Ouch incluso peor que cuadrático. Así que pypy está haciendo algo que funciona bien con cuerdas cortas, pero funciona mal para cuerdas más grandes.

 493
Author: John La Rooy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-19 01:00:50

No optimices prematuramente. Si no tienes ninguna razón para creer que hay un cuello de botella de velocidad causado por concatenaciones de cadenas, entonces simplemente sigue con + y +=:

s  = 'foo'
s += 'bar'
s += 'baz'

Dicho esto, si estás apuntando a algo como StringBuilder de Java, el lenguaje canónico de Python es agregar elementos a una lista y luego usar str.join para concatenar todos al final:

l = []
l.append('foo')
l.append('bar')
l.append('baz')

s = ''.join(l)
 239
Author: John Kugelman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-14 01:45:11

No lo hagas.

Es decir, en la mayoría de los casos es mejor generar la cadena completa de una sola vez en lugar de agregarla a una cadena existente.

Por ejemplo, no hacer: obj1.name + ":" + str(obj1.count)

En su lugar: use "%s:%d" % (obj1.name, obj1.count)

Que será más fácil de leer y más eficiente.

 34
Author: Winston Ewert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-14 02:06:59
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))

Que 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 casi tan eficiente como lo es para un método builtin.

 27
Author: Rafe Kettler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-14 01:42:55

Realmente depende de su aplicación. Si estás repasando cientos de palabras y quieres añadirlas todas a una lista, .join() es mejor. Pero si estás armando una oración larga, es mejor usar +=.

 9
Author: Ramy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-26 15:57:21

Si necesita hacer muchas operaciones de anexar para construir una cadena grande, puede usar StringIO o cStringIO. La interfaz es como un archivo. ie: you write para añadirle texto.

Si solo agregas dos cadenas, usa +.

 8
Author: Laurence Gonsalves,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-30 20:31:52

Básicamente, no hay diferencia. La única tendencia consistente es que Python parece ser cada vez más lento con cada versión... :(


List

%%timeit
x = []
for i in range(100000000):  # xrange on Python 2.7
    x.append('a')
x = ''.join(x)

Python 2.7

1 bucle, mejor de 3: 7.34 s por bucle

Python 3.4

1 bucle, mejor de 3: 7.99 s por bucle

Python 3.5

1 bucle, mejor de 3: 8.48 s por bucle

Python 3.6

1 bucle, mejor de 3: 9.93 s por bucle


String

%%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 bucle, mejor de 3: 9.08 s por bucle

Python 3.5

1 bucle, mejor de 3: 8.82 s por bucle

Python 3.6

1 bucle, al mejor de 3: 9.24 s por bucle

 4
Author: ostrokach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-10 18:14:16
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'
 3
Author: Rahul shrivastava,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-20 17:42:30

Añadir cadenas con __añadir__ función

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

Salida

Hello World
 0
Author: saigopi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-18 12:21:46