752 Stimmen

Wie kann ich in Python eine Zeichenkette an eine andere anhängen?

Wie kann ich eine Zeichenkette effizient an eine andere anhängen? Gibt es schnellere Alternativen zu:

var1 = "foo"
var2 = "bar"
var3 = var1 + var2

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)

768voto

John La Rooy Punkte 278961

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.

17 Stimmen

Interessant. Meinen Sie mit "jetzt" Python 3.x?

13 Stimmen

@Steve, Nein. Es ist mindestens in 2.6 vielleicht sogar 2.5

8 Stimmen

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

366voto

John Kugelman Punkte 327535

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)

0 Stimmen

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.

40 Stimmen

@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.

7 Stimmen

@Ben, in diesem Bereich hat es eine deutliche Verbesserung gegeben - siehe meine Antwort

64voto

Rafe Kettler Punkte 73546
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.

1 Stimmen

Was passiert, wenn str1 ein Leerzeichen ist? Wird der Whitespace gesetzt?

0 Stimmen

@JürgenK. Ja. Es behandelt leere Zeichenfolgen nicht anders. Es nimmt einfach alle Zeichenketten und setzt den Sperator dazwischen.

39voto

Winston Ewert Punkte 42219

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.

72 Stimmen

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

26 Stimmen

@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())

1 Stimmen

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.

32voto

Trenton Punkte 11118

Python 3.6 gibt uns f-strings die ein Genuss sind:

var1 = "foo"
var2 = "bar"
var3 = f"{var1}{var2}"
print(var3)                       # prints foobar

Innerhalb der geschweiften Klammern können Sie fast alles tun

print(f"1 + 1 == {1 + 1}")        # prints 1 + 1 == 2

CodeJaeger.com

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.

Powered by:

X