Come posso aggiungere una stringa a un'altra in Python?


594

Voglio un modo efficiente per aggiungere una stringa a un'altra in Python, oltre a quanto segue.

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

Esiste un buon metodo integrato da usare?


8
TL; DR: Se stai solo cercando il modo semplice di aggiungere le stringhe e non ti interessa l'efficienza:"foo" + "bar" + str(3)
Andrew

Risposte:


609

Se hai solo un riferimento a una stringa e concateni un'altra stringa fino alla fine, CPython ora casi speciali questo e cerca di estendere la stringa in posizione.

Il risultato finale è che l'operazione è ammortizzata O (n).

per esempio

s = ""
for i in range(n):
    s+=str(i)

era O (n ^ 2), ma ora è O (n).

Dalla fonte (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;
}

È abbastanza facile verificarlo empiricamente.

$ python -m timeit -s "s = ''" "per i in xrange (10): s + = 'a'"
1000000 loop, meglio di 3: 1,85 usec per loop
$ python -m timeit -s "s = ''" "per i in xrange (100): s + = 'a'"
10000 loop, meglio di 3: 16,8 usec per loop
$ python -m timeit -s "s = ''" "per i in xrange (1000): s + = 'a'"
10000 loop, meglio di 3: 158 usec per loop
$ python -m timeit -s "s = ''" "per i in xrange (10000): s + = 'a'"
1000 loop, meglio di 3: 1,71 msec per loop
$ python -m timeit -s "s = ''" "per i in xrange (100000): s + = 'a'"
10 loop, meglio di 3: 14,6 msec per loop
$ python -m timeit -s "s = ''" "per i in xrange (1000000): s + = 'a'"
10 loop, meglio di 3: 173 msec per loop

È importante tuttavia notare che questa ottimizzazione non fa parte delle specifiche Python. Per quanto ne so, è solo nell'implementazione di cPython. Lo stesso test empirico su pypy o jython, ad esempio, potrebbe mostrare le prestazioni O (n ** 2) precedenti.

$ pypy -m timeit -s "s = ''" "per i in xrange (10): s + = 'a'"
10000 loop, meglio di 3: 90,8 usec per loop
$ pypy -m timeit -s "s = ''" "per i in xrange (100): s + = 'a'"
1000 loop, meglio di 3: 896 usec per loop
$ pypy -m timeit -s "s = ''" "per i in xrange (1000): s + = 'a'"
100 loop, meglio di 3: 9,03 msec per loop
$ pypy -m timeit -s "s = ''" "per i in xrange (10000): s + = 'a'"
10 loop, meglio di 3: 89,5 msec per loop

Fin qui tutto bene, ma poi,

$ pypy -m timeit -s "s = ''" "per i in xrange (100000): s + = 'a'"
10 loop, meglio di 3: 12,8 secondi per loop

molto peggio del quadratico. Quindi il pypy sta facendo qualcosa che funziona bene con stringhe brevi, ma si comporta male per stringhe più grandi.


14
Interessante. Per "ora", intendi Python 3.x?
Steve Tjoa,

10
@Steve, No. È almeno in 2.6 forse anche in 2.5
John La Rooy il

8
Hai citato la PyString_ConcatAndDelfunzione ma hai incluso il commento per _PyString_Resize. Inoltre, il commento non stabilisce in realtà il tuo reclamo riguardo al Big-O
Winston Ewert

3
congratulazioni per aver sfruttato una funzione di CPython che farà strisciare il codice su altre implementazioni. Cattivo consiglio.
Jean-François Fabre

4
NON usare questo. Pep8 afferma esplicitamente: il codice dovrebbe essere scritto in modo da non svantaggiare altre implementazioni di Python (PyPy, Jython, IronPython, Cython, Psyco e simili , quindi dare questo esempio specifico come qualcosa da evitare poiché è così fragile."".join(str_a, str_b)
Eraw,

287

Non ottimizzare prematuramente. Se non hai motivo di credere che ci sia un collo di bottiglia per la velocità causato da concatenazioni di stringhe, segui semplicemente +e +=:

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

Detto questo, se stai mirando a qualcosa come StringBuilder di Java, il linguaggio canonico Python è quello di aggiungere elementi a un elenco e quindi utilizzarli str.joinper concatenarli alla fine:

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

s = ''.join(l)

Non so quali siano le implicazioni sulla velocità di costruire le tue stringhe come liste e poi .join () ing, ma trovo che sia generalmente il modo più pulito. Ho anche avuto grandi successi con l'utilizzo della notazione% s all'interno di una stringa per un motore di template SQL che ho scritto.
Richo,

25
@Richo L'uso di .join è più efficiente. Il motivo è che le stringhe di Python sono immutabili, quindi usando ripetutamente s + = more verranno allocate molte stringhe successivamente più grandi. .join genererà la stringa finale in una volta dalle sue parti costitutive.
Ben

5
@Ben, c'è stato un significativo miglioramento in questo settore - vedi la mia risposta
John La Rooy

41
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))

Che unisce str1 e str2 con uno spazio come separatori. Puoi anche fare "".join(str1, str2, ...). str.join()prende un iterabile, quindi dovresti mettere le stringhe in un elenco o una tupla.

È efficiente quanto si ottiene per un metodo incorporato.


Cosa succede se str1 è empy? Lo spazio bianco verrà impostato?
Jürgen K.,

38

Non farlo.

Cioè, nella maggior parte dei casi è meglio generare l'intera stringa in una volta sola piuttosto che aggiungere una stringa esistente.

Ad esempio, non fare: obj1.name + ":" + str(obj1.count)

Invece: usa "%s:%d" % (obj1.name, obj1.count)

Sarà più facile da leggere e più efficiente.


54
mi dispiace non c'è niente di più facile da leggere di (stringa + stringa) come il primo esempio, il secondo esempio potrebbe essere più efficiente, ma non più leggibile
JqueryToAddNumbers

23
@ExceptionSlayer, string + string è abbastanza facile da seguire. Ma "<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"trovo meno leggibile e soggetto a errori allora"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Winston Ewert

Questo non aiuta affatto quando quello che sto cercando di fare è l'equivalente approssimativo, diciamo, della "stringa. = Verifydata ()" di PHP / perl o simile.
Shadur,

@Shadur, il mio punto è che dovresti ripensarci, vuoi davvero fare qualcosa di equivalente o è meglio un approccio completamente diverso?
Winston Ewert,

1
E in questo caso la risposta a questa domanda è "No, perché questo approccio non copre il mio caso d'uso"
Shadur,

11

Python 3.6 ci dà le stringhe f , che sono una delizia:

var1 = "foo"
var2 = "bar"
var3 = f"{var1}{var2}"
print(var3)                       # prints foobar

Puoi fare quasi tutto all'interno delle parentesi graffe

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

10

Se è necessario eseguire molte operazioni di accodamento per creare una stringa di grandi dimensioni, è possibile utilizzare StringIO o cStringIO. L'interfaccia è come un file. cioè: devi writeaggiungere del testo ad esso.

Se stai solo aggiungendo due stringhe, usa semplicemente +.


9

dipende davvero dalla tua applicazione. Se esegui il ciclo tra centinaia di parole e desideri aggiungerle tutte in un elenco, .join()è meglio. Ma se stai mettendo insieme una frase lunga, stai meglio usando +=.


5

Fondamentalmente, nessuna differenza. L'unica tendenza coerente è che Python sembra rallentare con ogni versione ... :(


Elenco

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

Python 2.7

1 loop, meglio di 3: 7,34 s per loop

Python 3.4

1 loop, meglio di 3: 7.99 s per loop

Python 3.5

1 loop, meglio di 3: 8,48 s per loop

Python 3.6

1 loop, meglio di 3: 9,93 s per loop


Corda

%%timeit
x = ''
for i in range(100000000):  # xrange on Python 2.7
    x += 'a'

Python 2.7 :

1 loop, meglio di 3: 7,41 s per loop

Python 3.4

1 loop, meglio di 3: 9,08 s per loop

Python 3.5

1 loop, meglio di 3: 8,82 s per loop

Python 3.6

1 loop, meglio di 3: 9,24 s per loop


2
Immagino che dipenda. Ottengo 1.19 se 992 msrispettivamente su Python2.7
John La Rooy il

5

aggiungi stringhe con la funzione __add__

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

Produzione

Hello World

4
str + str2è ancora più breve.
Nik O'Lai,

2
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'

1
Il codice è carino, ma sarebbe utile avere una spiegazione di accompagnamento. Perché usare questo metodo piuttosto che le altre risposte in questa pagina?
CG

11
L'uso a.__add__(b)è identico alla scrittura a+b. Quando si concatenano le stringhe utilizzando l' +operatore, Python chiamerà il __add__metodo sulla stringa sul lato sinistro passando la stringa del lato destro come parametro.
Addie
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.