Wie füge ich in Python eine Zeichenfolge an eine andere an?

594

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?

user469652
quelle
8
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):

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.

John La Rooy
quelle
14
Interessant. Mit "jetzt" meinen Sie Python 3.x?
Steve Tjoa
10
@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.
Jean-François Fabre
4
Verwenden Sie dies NICHT. In Pep8 heißt es ausdrücklich: Code sollte so geschrieben werden, dass andere Implementierungen von Python (PyPy, Jython, IronPython, Cython, Psyco usw.) nicht benachteiligt werden. Dieses spezielle Beispiel sollte vermieden werden, da es so zerbrechlich ist. Bessere Verwendung"".join(str_a, str_b)
Eraw
287

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)
John Kugelman
quelle
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
John La Rooy
41
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.

Rafe Kettler
quelle
Was passiert, wenn str1 empy ist? Wird das Leerzeichen gesetzt?
Jürgen K.
38

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.

Winston Ewert
quelle
54
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

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

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

Laurence Gonsalves
quelle
9

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 +=.

Ramy
quelle
5

Grundsätzlich kein Unterschied. Der einzige konsistente Trend ist, dass Python mit jeder Version langsamer zu werden scheint ... :(


Liste

%%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


String

%%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

ostrokach
quelle
2
Ich denke es kommt darauf an. Ich erhalte 1.19 sund 992 msjeweils auf python2.7
John La Rooy
5

Fügen Sie Zeichenfolgen mit der Funktion __add__ hinzu

str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)

Ausgabe

Hello World
Sai Gopi N.
quelle
4
str + str2ist noch kürzer.
Nik O'Lai
2
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'
Rahul Shrivastava
quelle
1
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.
Addie