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?
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?
Risposte:
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.
PyString_ConcatAndDel
funzione ma hai incluso il commento per _PyString_Resize
. Inoltre, il commento non stabilisce in realtà il tuo reclamo riguardo al Big-O
"".join(str_a, str_b)
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.join
per concatenarli alla fine:
l = []
l.append('foo')
l.append('bar')
l.append('baz')
s = ''.join(l)
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.
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.
"<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())
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
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 write
aggiungere del testo ad esso.
Se stai solo aggiungendo due stringhe, usa semplicemente +
.
Fondamentalmente, nessuna differenza. L'unica tendenza coerente è che Python sembra rallentare con ogni versione ... :(
%%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
%%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
1.19 s
e 992 ms
rispettivamente su Python2.7
aggiungi stringhe con la funzione __add__
str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)
Produzione
Hello World
str + str2
è ancora più breve.
a='foo'
b='baaz'
a.__add__(b)
out: 'foobaaz'
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.
"foo" + "bar" + str(3)