TL; DR: Wenn Sie nur nach der einfachen Möglichkeit suchen, Zeichenfolgen anzuhängen, und sich die Effizienz nicht interessiert:"foo" + "bar" + str(3)
Andrew
Antworten:
609
Wenn Sie nur einen Verweis auf eine Zeichenfolge haben und eine andere Zeichenfolge bis zum Ende verketten, führt CPython dies jetzt in Sonderfällen durch und versucht, die Zeichenfolge an Ort und Stelle zu erweitern.
Das Endergebnis ist, dass die Operation O (n) abgeschrieben wird.
z.B
s =""for i in range(n):
s+=str(i)
Früher war es O (n ^ 2), jetzt ist es O (n).
Aus der Quelle (bytesobject.c):
voidPyBytes_ConcatAndDel(registerPyObject**pv,registerPyObject*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){registerPyObject*v;registerPyBytesObject*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 */return0;}
Es ist einfach genug, empirisch zu überprüfen.
$ python -m timeit -s "s = ''" "für i in xrange (10): s + = 'a'"
1000000 Schleifen, am besten 3: 1,85 usec pro Schleife
$ python -m timeit -s "s = ''" "für i in xrange (100): s + = 'a'"
10000 Schleifen, am besten 3: 16,8 usec pro Schleife
$ python -m timeit -s "s = ''" "für i in xrange (1000): s + = 'a'"
10000 Schleifen, am besten 3: 158 usec pro Schleife
$ python -m timeit -s "s = ''" "für i in xrange (10000): s + = 'a'"
1000 Schleifen, am besten 3: 1,71 ms pro Schleife
$ python -m timeit -s "s = ''" "für i in xrange (100000): s + = 'a'"
10 Schleifen, am besten 3: 14,6 ms pro Schleife
$ python -m timeit -s "s = ''" "für i in xrange (1000000): s + = 'a'"
10 Schleifen, am besten 3: 173 ms pro Schleife
Es ist jedoch wichtig zu beachten, dass diese Optimierung nicht Teil der Python-Spezifikation ist. Soweit ich weiß, ist es nur in der cPython-Implementierung. Dieselben empirischen Tests an Pypy oder Jython könnten beispielsweise die ältere O (n ** 2) -Leistung zeigen.
$ pypy -m timeit -s "s = ''" "für i in xrange (10): s + = 'a'"
10000 Schleifen, am besten 3: 90,8 usec pro Schleife
$ pypy -m timeit -s "s = ''" "für i in xrange (100): s + = 'a'"
1000 Schleifen, am besten 3: 896 usec pro Schleife
$ pypy -m timeit -s "s = ''" "für i in xrange (1000): s + = 'a'"
100 Schleifen, am besten 3: 9,03 ms pro Schleife
$ pypy -m timeit -s "s = ''" "für i in xrange (10000): s + = 'a'"
10 Schleifen, am besten 3: 89,5 ms pro Schleife
So weit so gut, aber dann
$ pypy -m timeit -s "s = ''" "für i in xrange (100000): s + = 'a'"
10 Schleifen, am besten 3: 12,8 Sekunden pro Schleife
autsch noch schlimmer als quadratisch. Pypy macht also etwas, das mit kurzen Saiten gut funktioniert, bei größeren Saiten jedoch schlecht funktioniert.
@Steve, Nein. Es ist mindestens in 2.6 vielleicht sogar 2.5
John La Rooy
8
Sie haben die PyString_ConcatAndDelFunktion zitiert , aber den Kommentar für eingefügt _PyString_Resize. Auch der Kommentar begründet nicht wirklich Ihren Anspruch bezüglich des Big-O
Winston Ewert
3
Herzlichen Glückwunsch zum Ausnutzen einer CPython-Funktion, mit der der Code bei anderen Implementierungen gecrawlt wird. Schlechter Rat.
Nicht vorzeitig optimieren. Wenn Sie keinen Grund zu der Annahme haben, dass es einen Geschwindigkeitsengpass gibt, der durch Zeichenfolgenverkettungen verursacht wird, bleiben Sie einfach bei +und +=:
s ='foo'
s +='bar'
s +='baz'
Das heißt, wenn Sie etwas wie Javas StringBuilder anstreben, besteht die kanonische Python-Sprache darin, Elemente zu einer Liste hinzuzufügen und str.joinsie am Ende zu verketten:
l =[]
l.append('foo')
l.append('bar')
l.append('baz')
s =''.join(l)
Ich weiß nicht, wie sich das Erstellen Ihrer Zeichenfolgen als Listen und das anschließende Verbinden von Zeichenfolgen auf die Geschwindigkeit auswirkt, aber ich finde, dass dies im Allgemeinen der sauberste Weg ist. Ich hatte auch große Erfolge mit der Verwendung der% s-Notation innerhalb einer Zeichenfolge für eine von mir geschriebene SQL-Template-Engine.
Richo
25
@Richo Die Verwendung von .join ist effizienter. Der Grund dafür ist, dass Python-Zeichenfolgen unveränderlich sind. Wenn Sie also wiederholt s + = more verwenden, werden viele nacheinander größere Zeichenfolgen zugewiesen. .join generiert die endgültige Zeichenfolge auf einmal aus seinen Bestandteilen.
Ben
5
@ Ben, es hat eine signifikante Verbesserung in diesem Bereich gegeben - siehe meine Antwort
Das verbindet str1 und str2 mit einem Leerzeichen als Trennzeichen. Sie können auch tun "".join(str1, str2, ...). str.join()nimmt eine iterable, so müssten Sie die Zeichenfolgen in eine Liste oder ein Tupel setzen.
Das ist ungefähr so effizient wie es für eine eingebaute Methode nur geht.
Es tut mir leid, dass es nichts einfacher zu lesen gibt als (Zeichenfolge + Zeichenfolge) wie das erste Beispiel. Das zweite Beispiel ist möglicherweise effizienter, aber nicht besser lesbar
JqueryToAddNumbers
23
@ExceptionSlayer, string + string ist ziemlich einfach zu folgen. Aber "<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"ich finde dann weniger lesbar und fehleranfällig"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Winston Ewert
Dies hilft überhaupt nicht, wenn ich versuche, das grobe Äquivalent von beispielsweise PHP / Perls "string. = Verifydata ()" oder ähnlichem zu sein.
Shadur
@ Shadur, mein Punkt ist, dass Sie noch einmal überlegen sollten, möchten Sie wirklich etwas Äquivalentes tun, oder ist ein ganz anderer Ansatz besser?
Winston Ewert
1
Und in diesem Fall lautet die Antwort auf diese Frage "Nein, weil dieser Ansatz meinen Anwendungsfall nicht abdeckt"
Shadur,
11
Python 3.6 gibt uns F-Strings , die eine Freude sind:
var1 ="foo"
var2 ="bar"
var3 = f"{var1}{var2}"print(var3)# prints foobar
In den geschweiften Klammern können Sie fast alles tun
Wenn Sie viele Anhängevorgänge ausführen müssen, um eine große Zeichenfolge zu erstellen, können Sie StringIO oder cStringIO verwenden. Die Schnittstelle ist wie eine Datei. dh: Sie müssen writeText anhängen.
Wenn Sie nur zwei Zeichenfolgen anhängen, verwenden Sie einfach +.
es hängt wirklich von Ihrer Anwendung ab. Wenn Sie Hunderte von Wörtern durchlaufen und sie alle in eine Liste einfügen möchten, .join()ist dies besser. Aber wenn Sie einen langen Satz zusammenstellen, ist es besser, ihn zu verwenden +=.
Code ist nett, aber es wäre hilfreich, eine begleitende Erklärung zu haben. Warum diese Methode anstelle der anderen Antworten auf dieser Seite verwenden?
CGMB
11
Die Verwendung a.__add__(b)ist identisch mit dem Schreiben a+b. Wenn Sie Zeichenfolgen mit dem +Operator verketten , ruft Python die __add__Methode für die Zeichenfolge auf der linken Seite auf und übergibt die Zeichenfolge auf der rechten Seite als Parameter.
"foo" + "bar" + str(3)
Antworten:
Wenn Sie nur einen Verweis auf eine Zeichenfolge haben und eine andere Zeichenfolge bis zum Ende verketten, führt CPython dies jetzt in Sonderfällen durch und versucht, die Zeichenfolge an Ort und Stelle zu erweitern.
Das Endergebnis ist, dass die Operation O (n) abgeschrieben wird.
z.B
Früher war es O (n ^ 2), jetzt ist es O (n).
Aus der Quelle (bytesobject.c):
Es ist einfach genug, empirisch zu überprüfen.
Es ist jedoch wichtig zu beachten, dass diese Optimierung nicht Teil der Python-Spezifikation ist. Soweit ich weiß, ist es nur in der cPython-Implementierung. Dieselben empirischen Tests an Pypy oder Jython könnten beispielsweise die ältere O (n ** 2) -Leistung zeigen.
So weit so gut, aber dann
autsch noch schlimmer als quadratisch. Pypy macht also etwas, das mit kurzen Saiten gut funktioniert, bei größeren Saiten jedoch schlecht funktioniert.
quelle
PyString_ConcatAndDel
Funktion zitiert , aber den Kommentar für eingefügt_PyString_Resize
. Auch der Kommentar begründet nicht wirklich Ihren Anspruch bezüglich des Big-O"".join(str_a, str_b)
Nicht vorzeitig optimieren. Wenn Sie keinen Grund zu der Annahme haben, dass es einen Geschwindigkeitsengpass gibt, der durch Zeichenfolgenverkettungen verursacht wird, bleiben Sie einfach bei
+
und+=
:Das heißt, wenn Sie etwas wie Javas StringBuilder anstreben, besteht die kanonische Python-Sprache darin, Elemente zu einer Liste hinzuzufügen und
str.join
sie am Ende zu verketten:quelle
Das verbindet str1 und str2 mit einem Leerzeichen als Trennzeichen. Sie können auch tun
"".join(str1, str2, ...)
.str.join()
nimmt eine iterable, so müssten Sie die Zeichenfolgen in eine Liste oder ein Tupel setzen.Das ist ungefähr so effizient wie es für eine eingebaute Methode nur geht.
quelle
Tu es nicht.
Das heißt, in den meisten Fällen ist es besser, die gesamte Zeichenfolge auf einmal zu generieren, als an eine vorhandene Zeichenfolge anzuhängen.
Tun Sie zum Beispiel nicht:
obj1.name + ":" + str(obj1.count)
Stattdessen: verwenden
"%s:%d" % (obj1.name, obj1.count)
Das ist leichter zu lesen und effizienter.
quelle
"<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"
ich finde dann weniger lesbar und fehleranfällig"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Python 3.6 gibt uns F-Strings , die eine Freude sind:
In den geschweiften Klammern können Sie fast alles tun
quelle
Wenn Sie viele Anhängevorgänge ausführen müssen, um eine große Zeichenfolge zu erstellen, können Sie StringIO oder cStringIO verwenden. Die Schnittstelle ist wie eine Datei. dh: Sie müssen
write
Text anhängen.Wenn Sie nur zwei Zeichenfolgen anhängen, verwenden Sie einfach
+
.quelle
es hängt wirklich von Ihrer Anwendung ab. Wenn Sie Hunderte von Wörtern durchlaufen und sie alle in eine Liste einfügen möchten,
.join()
ist dies besser. Aber wenn Sie einen langen Satz zusammenstellen, ist es besser, ihn zu verwenden+=
.quelle
Grundsätzlich kein Unterschied. Der einzige konsistente Trend ist, dass Python mit jeder Version langsamer zu werden scheint ... :(
Liste
Python 2.7
Python 3.4
Python 3.5
Python 3.6
String
Python 2.7 :
Python 3.4
Python 3.5
Python 3.6
quelle
1.19 s
und992 ms
jeweils auf python2.7Fügen Sie Zeichenfolgen mit der Funktion __add__ hinzu
Ausgabe
quelle
str + str2
ist noch kürzer.quelle
a.__add__(b)
ist identisch mit dem Schreibena+b
. Wenn Sie Zeichenfolgen mit dem+
Operator verketten , ruft Python die__add__
Methode für die Zeichenfolge auf der linken Seite auf und übergibt die Zeichenfolge auf der rechten Seite als Parameter.