String-Verkettung vs. String-Substitution in Python

98

In Python entgeht mir das Wo und Wann der Verwendung der Zeichenfolgenverkettung im Vergleich zur Zeichenfolgenersetzung. Ist dies (mehr) eine stilistische Entscheidung und keine praktische, da die Verkettung von Strings große Leistungssteigerungen erfahren hat?

Wie sollte man als konkretes Beispiel mit der Konstruktion flexibler URIs umgehen:

DOMAIN = 'http://stackoverflow.com'
QUESTIONS = '/questions'

def so_question_uri_sub(q_num):
    return "%s%s/%d" % (DOMAIN, QUESTIONS, q_num)

def so_question_uri_cat(q_num):
    return DOMAIN + QUESTIONS + '/' + str(q_num)

Bearbeiten: Es gab auch Vorschläge zum Verbinden einer Liste von Zeichenfolgen und zum Verwenden der benannten Substitution. Dies sind Varianten des zentralen Themas: Welcher Weg ist der richtige Weg, um es zu welcher Zeit zu tun? Danke für die Antworten!

gotgenes
quelle
Komisch, in Ruby ist die String-Interpolation im Allgemeinen schneller als die Verkettung ...
Keltia
Sie haben die Rückkehr vergessen. "" .join ([DOMAIN, QUESTIONS, str (q_num)])
Jimmy
Ich bin kein Ruby-Experte, aber ich würde wetten, dass die Interpolation schneller ist, da Zeichenfolgen in Ruby veränderbar sind. Strings sind unveränderliche Sequenzen in Python.
Gotgenes
1
nur ein kleiner Kommentar zu URIs. URIs sind nicht genau wie Zeichenfolgen. Es gibt URIs, daher müssen Sie beim Verketten oder Vergleichen sehr vorsichtig sein. Beispiel: Ein Server, der seine Darstellungen über http auf Port 80 liefert. Example.org (kein Slah am Ende) example.org/ (Schrägstrich) example.org:80/ (Slah + Port 80) sind die gleichen Uri, aber nicht die gleichen Zeichenfolge.
Karlcow

Antworten:

55

Die Verkettung ist laut meinem Computer (erheblich) schneller. Aber stilistisch bin ich bereit, den Preis für die Substitution zu zahlen, wenn die Leistung nicht kritisch ist. Nun, und wenn ich eine Formatierung benötige, muss ich nicht einmal die Frage stellen ... es gibt keine andere Möglichkeit, als Interpolation / Templating zu verwenden.

>>> import timeit
>>> def so_q_sub(n):
...  return "%s%s/%d" % (DOMAIN, QUESTIONS, n)
...
>>> so_q_sub(1000)
'http://stackoverflow.com/questions/1000'
>>> def so_q_cat(n):
...  return DOMAIN + QUESTIONS + '/' + str(n)
...
>>> so_q_cat(1000)
'http://stackoverflow.com/questions/1000'
>>> t1 = timeit.Timer('so_q_sub(1000)','from __main__ import so_q_sub')
>>> t2 = timeit.Timer('so_q_cat(1000)','from __main__ import so_q_cat')
>>> t1.timeit(number=10000000)
12.166618871951641
>>> t2.timeit(number=10000000)
5.7813972166853773
>>> t1.timeit(number=1)
1.103492206766532e-05
>>> t2.timeit(number=1)
8.5206360154188587e-06

>>> def so_q_tmp(n):
...  return "{d}{q}/{n}".format(d=DOMAIN,q=QUESTIONS,n=n)
...
>>> so_q_tmp(1000)
'http://stackoverflow.com/questions/1000'
>>> t3= timeit.Timer('so_q_tmp(1000)','from __main__ import so_q_tmp')
>>> t3.timeit(number=10000000)
14.564135316080637

>>> def so_q_join(n):
...  return ''.join([DOMAIN,QUESTIONS,'/',str(n)])
...
>>> so_q_join(1000)
'http://stackoverflow.com/questions/1000'
>>> t4= timeit.Timer('so_q_join(1000)','from __main__ import so_q_join')
>>> t4.timeit(number=10000000)
9.4431309007150048
Vinko Vrsalovic
quelle
10
Haben Sie Tests mit wirklich großen Zeichenfolgen (wie 100000 Zeichen) durchgeführt?
drnk
24

Vergessen Sie nicht die benannte Substitution:

def so_question_uri_namedsub(q_num):
    return "%(domain)s%(questions)s/%(q_num)d" % locals()
zu viel php
quelle
4
Dieser Code weist mindestens zwei schlechte Programmierpraktiken auf: Erwartung globaler Variablen (Domäne und Fragen werden nicht innerhalb der Funktion deklariert) und Übergabe von mehr Variablen als erforderlich an eine format () -Funktion. Downvoting, weil diese Antwort schlechte Codierungspraktiken lehrt.
Jperelli
12

Seien Sie vorsichtig, wenn Sie Zeichenfolgen in einer Schleife verketten! Die Kosten für die Verkettung von Zeichenfolgen sind proportional zur Länge des Ergebnisses. Looping führt Sie direkt in das Land des N-Quadrats. Einige Sprachen optimieren die Verkettung mit der zuletzt zugewiesenen Zeichenfolge, aber es ist riskant, sich auf den Compiler zu verlassen, um Ihren quadratischen Algorithmus auf linear zu optimieren. Verwenden Sie am besten das Grundelement ( join?), Das eine ganze Liste von Zeichenfolgen verwendet, eine einzelne Zuordnung vornimmt und alle auf einmal verkettet.

Norman Ramsey
quelle
16
Das ist nicht aktuell. In den neuesten Versionen von Python wird ein versteckter Zeichenfolgenpuffer erstellt, wenn Sie Zeichenfolgen in einer Schleife verketten.
Seun Osewa
5
@Seun: Ja, wie gesagt, einige Sprachen werden optimiert, aber es ist eine riskante Praxis.
Norman Ramsey
11

"Da die String-Verkettung große Leistungssteigerungen erfahren hat ..."

Wenn es auf die Leistung ankommt, ist dies gut zu wissen.

Leistungsprobleme, die ich gesehen habe, sind jedoch nie auf Zeichenfolgenoperationen zurückzuführen. Ich habe im Allgemeinen Probleme mit E / A, Sortieren und O ( n 2 ) -Operationen sind die Engpässe.

Bis String-Operationen die Leistungsbegrenzer sind, bleibe ich bei Dingen, die offensichtlich sind. Meistens ist dies eine Ersetzung, wenn es sich um eine Zeile oder weniger handelt, eine Verkettung, wenn es sinnvoll ist, und ein Vorlagenwerkzeug (wie Mako), wenn es groß ist.

S.Lott
quelle
10

Was Sie verketten / interpolieren möchten und wie Sie das Ergebnis formatieren möchten, sollte Ihre Entscheidung bestimmen.

  • Mit der String-Interpolation können Sie einfach Formatierungen hinzufügen. Tatsächlich funktioniert Ihre String-Interpolationsversion nicht mit Ihrer Verkettungsversion. Es wird tatsächlich ein zusätzlicher Schrägstrich vor dem q_numParameter hinzugefügt. Um dasselbe zu tun, müssten Sie return DOMAIN + QUESTIONS + "/" + str(q_num)in dieses Beispiel schreiben .

  • Die Interpolation erleichtert das Formatieren von Zahlen. "%d of %d (%2.2f%%)" % (current, total, total/current)wäre in Verkettungsform viel weniger lesbar.

  • Die Verkettung ist nützlich, wenn Sie keine feste Anzahl von Elementen zum String-Ize haben.

Beachten Sie auch, dass Python 2.6 eine neue Version der Zeichenfolgeninterpolation einführt, die als Zeichenfolgenvorlagen bezeichnet wird :

def so_question_uri_template(q_num):
    return "{domain}/{questions}/{num}".format(domain=DOMAIN,
                                               questions=QUESTIONS,
                                               num=q_num)

String-Templating soll schließlich die% -Interpolation ersetzen, aber das wird, glaube ich, noch eine ganze Weile nicht passieren.

Tim Lesher
quelle
Nun, es wird immer dann passieren, wenn Sie sich für Python 3.0 entscheiden. Siehe auch Peters Kommentar für die Tatsache, dass Sie mit dem Operator% ohnehin benannte Ersetzungen vornehmen können.
John Fouhy
"Verkettung ist nützlich, wenn Sie keine feste Anzahl von Elementen zum String-Ize haben." - Du meinst eine Liste / ein Array? Könnten Sie in diesem Fall nicht einfach beitreten ()?
Strager
"Könntest du dich ihnen nicht einfach anschließen?" - Ja (vorausgesetzt, Sie möchten einheitliche Trennzeichen zwischen den Elementen). Das Verständnis von Listen und Generatoren funktioniert hervorragend mit string.join.
Tim Lesher
1
"Nun, es wird passieren, wenn Sie sich für Python 3.0 entscheiden" - Nein, py3k unterstützt weiterhin den% -Operator. Der nächste mögliche Abschreibungspunkt ist 3.1, also hat es noch etwas Leben in sich.
Tim Lesher
2
2 Jahre später ... Python 3.2 steht kurz vor der Veröffentlichung und die Interpolation im% -Stil ist immer noch in Ordnung.
Corey Goldberg
8

Ich habe nur aus Neugier die Geschwindigkeit verschiedener Methoden zur Verkettung / Ersetzung von Zeichenfolgen getestet. Eine Google-Suche zu diesem Thema hat mich hierher gebracht. Ich dachte, ich würde meine Testergebnisse veröffentlichen, in der Hoffnung, dass es jemandem bei der Entscheidung helfen könnte.

    import timeit
    def percent_():
            return "test %s, with number %s" % (1,2)

    def format_():
            return "test {}, with number {}".format(1,2)

    def format2_():
            return "test {1}, with number {0}".format(2,1)

    def concat_():
            return "test " + str(1) + ", with number " + str(2)

    def dotimers(func_list):
            # runs a single test for all functions in the list
            for func in func_list:
                    tmr = timeit.Timer(func)
                    res = tmr.timeit()
                    print "test " + func.func_name + ": " + str(res)

    def runtests(func_list, runs=5):
            # runs multiple tests for all functions in the list
            for i in range(runs):
                    print "----------- TEST #" + str(i + 1)
                    dotimers(func_list)

... Nach dem Ausführen runtests((percent_, format_, format2_, concat_), runs=5)stellte ich fest, dass die% -Methode auf diesen kleinen Zeichenfolgen etwa doppelt so schnell war wie die anderen. Die Concat-Methode war immer die langsamste (kaum). Es gab sehr kleine Unterschiede beim Wechseln der Positionen in der format()Methode, aber das Wechseln der Positionen war immer mindestens 0,01 langsamer als bei der regulären Formatmethode.

Stichprobe der Testergebnisse:

    test concat_()  : 0.62  (0.61 to 0.63)
    test format_()  : 0.56  (consistently 0.56)
    test format2_() : 0.58  (0.57 to 0.59)
    test percent_() : 0.34  (0.33 to 0.35)

Ich habe diese ausgeführt, weil ich in meinen Skripten die Verkettung von Zeichenfolgen verwende, und ich habe mich gefragt, wie hoch die Kosten waren. Ich habe sie in verschiedenen Reihenfolgen ausgeführt, um sicherzustellen, dass nichts stört oder dass eine bessere Leistung als erste oder letzte erzielt wird. "%s" + ("a" * 1024)Nebenbei bemerkt, ich habe einige längere String-Generatoren in diese Funktionen wie und reguläres Concat war fast dreimal so schnell (1.1 vs 2.8) wie mit den Methoden formatund %. Ich denke, es hängt von den Saiten ab und davon, was Sie erreichen wollen. Wenn die Leistung wirklich wichtig ist, ist es möglicherweise besser, verschiedene Dinge auszuprobieren und zu testen. Ich neige dazu, die Lesbarkeit der Geschwindigkeit vorzuziehen, es sei denn, die Geschwindigkeit wird zum Problem, aber das bin nur ich. Also mochte ich mein Kopieren / Einfügen nicht, ich musste 8 Leerzeichen auf alles setzen, damit es richtig aussah. Ich benutze normalerweise 4.

Cj Welborn
quelle
1
Sie sollten ernsthaft überlegen, was Sie wie profilieren. Zum einen ist Ihr Concat langsam, weil Sie zwei Str-Casts haben. Bei Strings ist das Ergebnis das Gegenteil, da String Concat tatsächlich schneller ist als alle Alternativen, wenn nur drei Strings betroffen sind.
Justus Wingert
@ JustusWingert, das ist jetzt zwei Jahre alt. Ich habe viel gelernt, seit ich diesen 'Test' gepostet habe. Ehrlich gesagt, heutzutage benutze ich str.format()und str.join()über normale Verkettung. Ich halte auch Ausschau nach 'F-Strings' aus PEP 498 , das kürzlich akzeptiert wurde. str()Ich bin mir sicher, dass Sie mit den Anrufen, die sich auf die Leistung auswirken, Recht haben. Ich hatte keine Ahnung, wie teuer Funktionsaufrufe damals waren. Ich denke immer noch, dass Tests durchgeführt werden sollten, wenn Zweifel bestehen.
Cj Welborn
Nach einem kurzen Test mit join_(): return ''.join(["test ", str(1), ", with number ", str(2)])scheint joines auch langsamer als der Prozentsatz zu sein.
gaborous
4

Denken Sie daran, Stilentscheidungen sind praktische Entscheidungen, wenn Sie jemals vorhaben, Ihren Code zu pflegen oder zu debuggen :-) Es gibt ein berühmtes Zitat von Knuth (möglicherweise unter Berufung auf Hoare?): "Wir sollten kleine Effizienzgewinne vergessen, etwa 97% der Zeit: vorzeitige Optimierung ist die Wurzel allen Übels. "

Solange Sie darauf achten, eine O (n) -Aufgabe nicht in eine O (n 2 ) -Aufgabe umzuwandeln, würde ich mich für das entscheiden, was für Sie am einfachsten zu verstehen ist.

John Fouhy
quelle
0

Ich benutze Substitution, wo immer ich kann. Ich verwende die Verkettung nur, wenn ich einen String in einer for-Schleife aufbaue.

Draemon
quelle
7
"Erstellen eines Strings in einer for-Schleife" - häufig ist dies ein Fall, in dem Sie '' .join und einen Generatorausdruck verwenden können.
John Fouhy
-1

In diesem Fall ist es eigentlich richtig, Pfade zu verwenden os.path.join. Keine Verkettung oder Interpolation von Zeichenfolgen

Hoskeri
quelle
1
Dies gilt für Betriebssystempfade (wie in Ihrem Dateisystem), jedoch nicht für die Erstellung eines URI wie in diesem Beispiel. URIs haben immer '/' als Trennzeichen.
Andre Blum