Ich möchte eine effiziente Möglichkeit, eine Zeichenfolge in Python an eine andere anzuhängen, mit Ausnahme der folgenden.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
Gibt es eine gute eingebaute Methode?
Ich möchte eine effiziente Möglichkeit, eine Zeichenfolge in Python an eine andere anzuhängen, mit Ausnahme der folgenden.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
Gibt es eine gute eingebaute Methode?
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
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 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.
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 +=
:
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.join
sie am Ende zu verketten:
l = []
l.append('foo')
l.append('bar')
l.append('baz')
s = ''.join(l)
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))
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.
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.
"<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())
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 +
.
Grundsätzlich kein Unterschied. Der einzige konsistente Trend ist, dass Python mit jeder Version langsamer zu werden scheint ... :(
%%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,93 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
1.19 s
und 992 ms
jeweils auf python2.7
Fügen Sie Zeichenfolgen mit der Funktion __add__ hinzu
str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)
Ausgabe
Hello World
str + str2
ist noch kürzer.
a='foo'
b='baaz'
a.__add__(b)
out: 'foobaaz'
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)