Wie kann ich eine Zeichenkette effizient an eine andere anhängen? Gibt es schnellere Alternativen zu:
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
Wie kann ich eine Zeichenkette effizient an eine andere anhängen? Gibt es schnellere Alternativen zu:
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
Wenn Sie nur einen Verweis auf eine Zeichenkette haben und eine weitere Zeichenkette an das Ende anhängen, versucht CPython nun in einem Sonderfall, die Zeichenkette an Ort und Stelle zu verlängern.
Das Endergebnis ist, dass die Operation mit O(n) amortisiert wird.
z.B..
s = ""
for i in range(n):
s+=str(i)
war früher O(n^2), aber jetzt ist es O(n).
Aus dem Quelltext (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;
}
Das lässt sich empirisch leicht nachprüfen.
$ 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
Es ist wichtig Es ist jedoch zu beachten, dass diese Optimierung nicht Teil der Python-Spezifikation ist. Sie ist nur in der cPython-Implementierung enthalten, soweit ich weiß. Derselbe empirische Test mit pypy oder jython könnte zum Beispiel die ältere O(n**2) Leistung zeigen.
$ 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
So weit so gut, aber dann,
$ pypy -m timeit -s"s=''" "for i in xrange(100000):s+='a'"
10 loops, best of 3: 12.8 sec per loop
ach, noch schlimmer als quadratisch. Pypy macht also etwas, das bei kurzen Strings gut funktioniert, aber bei größeren Strings schlecht abschneidet.
Sie zitieren die PyString_ConcatAndDel
Funktion, sondern auch den Kommentar für _PyString_Resize
. Außerdem belegt der Kommentar nicht wirklich Ihre Behauptung bezüglich des Big-O
Optimieren Sie nicht voreilig. Wenn Sie keinen Grund zur Annahme haben, dass es einen Geschwindigkeitsengpass gibt, der durch String-Verkettungen verursacht wird, dann bleiben Sie einfach bei +
y +=
:
s = 'foo'
s += 'bar'
s += 'baz'
Das heißt, wenn Sie etwas wie den StringBuilder von Java anstreben, ist das kanonische Python-Idiom, Elemente zu einer Liste hinzuzufügen und dann str.join
um sie alle am Ende zu verketten:
l = []
l.append('foo')
l.append('bar')
l.append('baz')
s = ''.join(l)
Ich weiß nicht, welche Auswirkungen es auf die Geschwindigkeit hat, wenn Sie Ihre Zeichenketten als Listen erstellen und sie dann mit .join()ing verbinden, aber ich finde, dass dies im Allgemeinen der sauberste Weg ist. Ich habe auch große Erfolge mit der Verwendung von %s Notation innerhalb einer Zeichenfolge für eine SQL-Templating-Engine, die ich geschrieben habe.
@Richo Die Verwendung von .join ist effizienter. Der Grund dafür ist, dass Python-Zeichenfolgen unveränderlich sind, so dass bei wiederholter Verwendung von s += more viele aufeinanderfolgende größere Zeichenfolgen zugewiesen werden. .join erzeugt die endgültige Zeichenkette in einem Durchgang aus ihren Bestandteilen.
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))
Das verbindet str1 und str2 mit einem Leerzeichen als Trennzeichen. Sie können auch "".join(str1, str2, ...)
. str.join()
nimmt eine Iterable, also müssen Sie die Strings in eine Liste oder ein Tupel packen.
Das ist die effizienteste Methode, die es für eine eingebaute Methode gibt.
Tun Sie es nicht.
Das heißt, in den meisten Fällen ist es besser, die gesamte Zeichenkette in einem Durchgang zu erzeugen, als sie an eine bestehende Zeichenkette anzuhängen.
Zum Beispiel, nicht tun: obj1.name + ":" + str(obj1.count)
Stattdessen: verwenden Sie "%s:%d" % (obj1.name, obj1.count)
Das ist einfacher zu lesen und effizienter.
Es tut mir leid, es gibt nichts einfacher zu lesen als ( string + string ) wie das erste Beispiel, das zweite Beispiel ist vielleicht effizienter, aber nicht besser lesbar
@ExceptionSlayer, string + string ist ziemlich einfach zu verstehen. Aber "<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"
finde ich weniger lesbar und fehleranfällig als "<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Das hilft überhaupt nicht, wenn das, was ich zu tun versuche, das ungefähre Äquivalent zu, sagen wir, PHP/perl's "string .= verifydata()" oder ähnlich ist.
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.
17 Stimmen
TL;DR: Wenn Sie nur nach einer einfachen Möglichkeit suchen, Zeichenketten anzuhängen, und Ihnen die Effizienz egal ist:
"foo" + "bar" + str(3)