In Python möchte ich auf effiziente Weise eine Zeichenfolge an eine andere anhängen.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
Gibt es eine gute eingebaute Methode zu verwenden?
Wenn Sie nur einen Verweis auf eine Zeichenfolge haben und eine andere Zeichenfolge bis zum Ende verketten, macht CPython dies jetzt in besonderen Fällen 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):
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 ist leicht genug, empirisch zu verifizieren.
$ python -m timeit -s "s = ''" "für i in xrange (10): s + = 'a'" 1000000 Schleifen, am besten von 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 im x-Bereich (1000000): s + = 'a' " 10 Schleifen, am besten 3: 173 ms pro Schleife
Es ist wichtig Beachten Sie jedoch, dass diese Optimierung nicht Teil der Python -Spezifikation ist. Soweit ich weiß, ist es nur in der cPython-Implementierung. Dieselben empirischen Tests mit 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, aber für größere Saiten schlecht funktioniert.
Nicht vorzeitig optimieren. Wenn Sie keinen Grund zur 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 auf etwas wie Javas StringBuilder abzielen, besteht das kanonische Python-Idiom darin, Elemente zu einer Liste hinzuzufügen und dann str.join
zu verwenden, um sie alle am Ende zu verketten:
l = []
l.append('foo')
l.append('bar')
l.append('baz')
s = ''.join(l)
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.
Zum Beispiel nicht tun: obj1.name + ":" + str(obj1.count)
Stattdessen: benutze "%s:%d" % (obj1.name, obj1.count)
Das wird einfacher zu lesen und effizienter.
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, ...)
ausführen. str.join()
nimmt einen iterablen Wert an, sodass Sie die Zeichenfolgen in eine Liste oder ein Tupel einfügen müssen.
Das ist ungefähr so effizient wie bei einer eingebauten Methode.
Wenn Sie viele Anhängeoperationen 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 write
, um Text daran anzuhä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 einer Liste anhängen möchten, ist .join()
besser. Aber wenn Sie einen langen Satz zusammensetzen, ist es besser, +=
zu verwenden.
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 machen
print(f"1 + 1 == {1 + 1}") # prints 1 + 1 == 2
Grundsätzlich kein Unterschied. Der einzig beständige Trend ist, dass Python mit jeder Version langsamer wird ... :(
%%timeit
x = []
for i in range(100000000): # xrange on Python 2.7
x.append('a')
x = ''.join(x)
Python 2.7
1 Schleife, am besten 3: 7.34 s pro Schleife
Python 3.4
1 Schleife, am besten 3: 7,99 s pro Schleife
Python 3.5
1 Schleife, am besten 3: 8,48 s pro Schleife
Python 3.6
1 Schleife, am besten 3: 9,9 s pro Schleife
%%timeit
x = ''
for i in range(100000000): # xrange on Python 2.7
x += 'a'
Python 2.7:
1 Schleife, am besten 3: 7,41 s pro Schleife
Python 3.4
1 Schleife, am besten 3: 9.08 s pro Schleife
Python 3.5
1 Schleife, am besten 3: 8,82 s pro Schleife
Python 3.6
1 Schleife, am besten 3: 9.24 s pro Schleife
a='foo'
b='baaz'
a.__add__(b)
out: 'foobaaz'
fügen Sie Zeichenfolgen mit der Funktion _ ADD _ hinzu
str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)
Ausgabe
Hello World