Was ist besser? a b und c enthalten strings. d=a+b+c besser als d="{}{}{}".format(a,b,c) ? Hermann der hier nicht an Lesbarkeit denkt. -- http://www.hermann-riemann.de
Hermann Riemann schrieb am 25.08.2017 um 07:45:
Was ist besser? a b und c enthalten strings.
Text-Strings? Byte-Strings? Definitiv Strings oder auch mal Bytearrays, Memoryviews, NumPy-Arrays oder sowas?
d=a+b+c besser als d="{}{}{}".format(a,b,c) ?
Ersteres finde ich in dem Fall deutlich lesbarer.
Hermann der hier nicht an Lesbarkeit denkt.
Woran dann? An Wiederverwendbarkeit? Code-Schönheit? Pulitzerpreisfähigkeit? Geschwindigkeit? Bei ersteren kann ich so pauschal nichts sagen. Bei letzterem hilft "timeit". Wenn es kurze Strings sind, ist es vermutlich eh egal. Aber ich vermute mal, dass die erste Variante dann zumindest messbar schneller ist. Wenn es wirklich lange Strings sind macht es vielleicht einen Unterschied, aber auch noch nicht unbedingt bei drei Strings. Hängt auch sehr von deiner Python-Version ab. Stefan
On 2017-08-25 07:45, Hermann Riemann wrote:> Was ist besser?
a b und c enthalten strings.
d=a+b+c besser als d="{}{}{}".format(a,b,c) ?
Die zweite Variante ist das übliche Idiom, wenn es um die Kombination von Strings geht. Ein Vorteil ist auch, dass man sieht, dass das Ergebnis `d` ein String ist. Die erste Variante hat wiederum den Vorteil, dass sie kürzer und etwas übersichtlicher ist. Es ist offensichtlich, dass `a`, `b` und `c` so beschaffen sind, dass man sie "addieren" kann. Bei der der zweiten Variante könnten `a`, `b` und `c` auch alle möglichen Objekte sein, die sich in einen String umwandeln lassen (was in Python praktisch immer der Fall ist). Die in einem anderen Beitrag genannte Variante mit `"".join` würde ich hier nicht nehmen. Diese Variante wäre aber die erste Wahl, wenn man viele Strings verbinden will, die dann sehr wahrscheinlich auch in einer Liste oder einem anderen Iterable vorliegen werden. Über die Laufzeit würde ich mir erst mal keine Gedanken machen. Ob man diese Codestelle optimieren muss oder ob ganz andere Stellen im Code einen viel größeren Einfluss haben, zeigt sich erst später beim Profiling.
der hier nicht an Lesbarkeit denkt.
Ich aber. :-) Lesbarkeit ist meiner Meinung nach ein Kriterium, das man immer berücksichtigen sollte. Damit meine ich _nicht_, dass sich der Lesbarkeit immer alle anderen Kriterien unterordnen sollte. Ich denke, ich würde in meinem Code die zweite Variante verwenden. Viele Grüße Stefan
a b und c enthalten strings.
d=a+b+c besser als # Variante 1 d="{}{}{}".format(a,b,c) ? # Variante 2
Die zweite Variante ist das übliche Idiom, wenn es um die Kombination von Strings geht.
Wirklich? Himmel hilf! Das wäre wirklich die letzte Version, die mir einfallen würde - nicht nur, weil ich, der ich die Flexibilität der format-Methode praktisch nie benötige, diese selten verwende. PEP 20.2, "Explicit is better than implicit." PEP 20.3, "Simple is better than complex." Wenn ich drei Variablen habe, von denen ich weiß, daß es Strings sind, verwende ich keinen Code, der auch beliebigen anderen Input stillschweigend (implizit) konvertiert. Wenn ich addieren will, addiere ich. Von daher spricht nichts gegen Variante 1: d = a + b + c PEP 20.7, "Readability counts." Variante 1 ist prima lesbar, aber möglicherweise besteht tatsächlich ds Bedürfnis, dem Leser zu "beweisen", daß das Ergebnis ein String ist. Dann erfüllt die "Variante 3" den Zweck: d = ''.join([a, b, c]) (die sich auch immer für inkrementell zusammengebaute Strings anbietet). Sie hat den weiteren Vorteil, daß sie fehlschlägt, wenn eine der Eingabevariablen kein String ist: PEP 20.10, "Errors should never pass silently." Ich kann sogar die Lesbar- und auch Änderbarkeit noch auf die Spitze treiben: d = ''.join([a, # Erklärung des ganz woanders erzeugten Wertes a b, # dto. für b c, # dto. für c ]) Dies ist sehr leicht änderbar, wenn ein Element hinzukommt, geändert wird oder entfällt. Vergleichs-Tools arbeiten zeilenorientiert. Die "Variante 3" konvertiert Nicht-Strings stillschweigend. Was aber, wenn ich ganz selbstverständlich davon ausgehe, daß es Strings sind, und eine Abweichung hiervon ein sicheres Zeichen für einen Fehler ist? Ich verplempere nicht nur eine Menge Rechenzeit, sondern verberge auch noch den Fehler: PEP 20.10, "Errors should never pass silently." Wenn ich die implizite String-Konversion will, kann ich das auch so erreichen: d = ''.join(map(str, [a, b, c])) Damit weise ich darauf hin, daß ich damit rechne, daß die Eingabewerte möglicherweise keine Strings sind, aber in solche konvertiert werden können: PEP 20.11, "Unless explicitly silenced." Schlußendlich hat Variante 2 noch einen weiteren Nachteil: sie ist umständlich zu ändern. Wenn sich die Anzahl der zu verkettenden Strings ändert, muß sowohl ein "{}" hinzugefügt oder gelöscht *als auch* die eigentliche Änderung vorgenommen werden. Bei drei Argumenten mag das gerade noch vertretbar sein; aber schon bei wenig mehr fängt die Zählerei an. Also, ganz eindeutig: Variante 1 oder 3, aber keinesfalls 2. -- Schönen Gruß, Tobias
Am 25.08.17 um 10:28 schrieb Tobias Herp:
Dann erfüllt die "Variante 3" den Zweck:
d = ''.join([a, b, c])
...
Die "Variante 3" konvertiert Nicht-Strings stillschweigend. Was aber, wenn ich ganz selbstverständlich davon ausgehe, daß es Strings sind, und eine Abweichung hiervon ein sicheres Zeichen für einen Fehler ist? Ich verplempere nicht nur eine Menge Rechenzeit, sondern verberge auch noch den Fehler:
Das stimmt aber nun nicht:
''.join(['a', 2]) TypeError Traceback (most recent call last) ''.join(['a', 2]) TypeError: sequence item 1: expected str instance, int found
Mike
On 2017-08-25 10:28, Tobias Herp wrote:
a b und c enthalten strings. d=a+b+c besser als # Variante 1 d="{}{}{}".format(a,b,c) ? # Variante 2
Die zweite Variante ist das übliche Idiom, wenn es um die Kombination von Strings geht.
Wirklich? Himmel hilf!
Erst mal vorweg. Ich denke, dass es für dieses Problem (Strings zusammenfassen) nicht so kritisch ist, welche der drei Varianten (`format`, `+` oder `join`) man verwendet. Als ich schrieb, ich würde Variante 2 verwenden, meinte ich damit eine Präferenz, aber nicht, dass die anderen beiden Varianten "falsch" wären oder davon abzuraten wäre.
Das wäre wirklich die letzte Version, die mir einfallen würde - nicht nur, weil ich, der ich die Flexibilität der format-Methode praktisch nie benötige, diese selten verwende.
Ich und anscheinend auch andere verwenden `format` mittlerweile dort, wo sie früher Prozent-Formatierungen verwendet haben. Dann wird die Verwendung von `format` relativ häufig. (Mir ist klar, dass man über "`format` oder nicht?" ausgiebig diskutieren kann und ich gehe davon aus, dass das bei der Einführung von `format` auch passiert ist.)
PEP 20.2, "Explicit is better than implicit." PEP 20.3, "Simple is better than complex." [...]
In meine Präferenz spielen aus PEP 20 mit hinein: - Readability counts. Interessant finde ich, dass wir beide "Readability counts" anführen, aber unterschiedliche Dinge lesbar finden. - There should be one-- and preferably only one --obvious way to do it. _Den_ offensichtlichen Weg gibt es hier anscheinend nicht, aber die Verwendung von `format` ist aus meiner Sicht "the most obvious". Ich finde, wenn ich drei Strings mit "{}+{}-{}".format(a, b, c) zu einem neuen String kombiniere, sollte der Code nicht ganz anders aussehen, wenn keine Zeichen dazwischen stehen. Ich denke, hier spielt hinein, welches Idiom wir in dem Code jeweils sehen - einen String aus einem Template erzeugen (siehe auch die Mail von Ole) oder eine Reihe von Strings verketten. Dieses Idiom würde ich eher sehen, wenn die Strings schon in einer Liste vorliegen. Wenn es darum geht, möglichst fehlerarmen und lesbaren Code zu schreiben, ist mein Ansatz, möglichst geradlinigen, "langweiligen" Code zu schreiben. Ich verwende sehr selten explizite Typ-Prüfungen und normalerweise würde ich nicht Code extra so schreiben, dass er möglichst viele implizite Typ-Prüfungen durchführt. Wenn der Code leicht lesbar ist, ist es wahrscheinlicher, dass Fehler schon beim Schreiben auffallen beziehungsweise gar nicht erst gemacht werden. Die Typ-Fehler versuche ich eher, mit automatisierten Tests zu finden. Aber auch da verwende ich selten _explizite_ Typ-Prüfungen. Ein damit zusammenhängender Aspekt ist Kontext: Bei meiner Antwort bin ich (unbewusst) davon ausgegangen, dass in der Praxis die Namen nicht `a`, `b` und `c` heißen und dass zu erkennen ist, wo die Werte herkommen. Das reduziert die Wahrscheinlichkeit von falschen Typen schon mal ganz erheblich, so dass das bei der Wahl des konkreten Codes kaum noch eine Rolle spielt.
Ich kann sogar die Lesbar- und auch Änderbarkeit noch auf die Spitze treiben:
d = ''.join([a, # Erklärung des ganz woanders erzeugten Wertes a b, # dto. für b c, # dto. für c ])
Ich bin mir nicht so sicher, ob das den Code wirklich lesbarer macht. Ich würde eher versuchen, sprechende Bezeichner zu verwenden und die Funktion/Methode so kurz zu halten, dass auch ohne Kommentare erkennbar ist, um was für Werte es sich handelt. In extremen Fällen können zusätzliche Kommentare sinnvoll sein, aber ich finde nicht, dass der Code damit automatisch lesbarer wird (aber unter Umständen besser wartbar). Viele Grüße Stefan
Mike Müller schrieb am 25.08.2017 um 11:18:
Am 25.08.17 um 10:28 schrieb Tobias Herp:
Dann erfüllt die "Variante 3" den Zweck:
d = ''.join([a, b, c])
...
Die "Variante 3" konvertiert Nicht-Strings stillschweigend.
Ok, hier ist mir ein Tippfehler unterlaufen; es sollte "Variante 2" heißen und trifft auf jede Template-Variante zu, egal ob als '{}'.format oder '%s' % ...
Was aber, wenn ich ganz selbstverständlich davon ausgehe, daß es Strings sind, und eine Abweichung hiervon ein sicheres Zeichen für einen Fehler ist? Ich verplempere nicht nur eine Menge Rechenzeit, sondern verberge auch noch den Fehler:
Das stimmt aber nun nicht:
''.join(['a', 2]) TypeError Traceback (most recent call last) ''.join(['a', 2]) TypeError: sequence item 1: expected str instance, int found
Das Verplempern von Rechenzeit bezog sich auch nicht auf die join-, sondern auf die grausliche .format-Variante ... Was Du ausprobiert hast, ist genau, was ich meine: wenn der join-Aufruf erfolgreich war, waren auch alle Argumente korrektermaßen Strings. Ansonsten wurde möglicherweise die aktuelle Funktion falsch aufgerufen, und die implizite Konversion würde den Fehler verbergen, der sich ansonsten durch den TypeError bemerkbar machen würde. -- Tobias
Servus Hermann! Am 25.08.2017 um 07:45 schrieb Hermann Riemann:
d=a+b+c besser als d="{}{}{}".format(a,b,c) ? Es gibt gute Gründe, warum es mehrere Formulierungen gibt um Strings zu addieren. Jede Formulierung hat Ihre Vor- und Nachteile, und keine ist sinnlos. Lies die Doku oder frage hier konkreter nach einer Lösung.
Es gibt keine beste Lösung per se. Beste Grüße Volker -- ========================================================= inqbus Scientific Computing Dr. Volker Jaenisch Richard-Strauss-Straße 1 +49(08861) 690 474 0 86956 Schongau-West http://www.inqbus.de =========================================================
Am 25.08.2017 um 07:45 schrieb Hermann Riemann:
Was ist besser? a b und c enthalten strings.
d=a+b+c besser als d="{}{}{}".format(a,b,c) ?
In diesem Fall ist das fast egal. Auch die Performance wird nahezu identisch sein. Ich würde den + Operator bevorzugen. Was man nicht machen sollte ist eine Konstruktion wie s = '' for i in range(1000): s = s + 'x' Hier wird 1001 mal ein str instantiiert und 1000 müssen vom CG wieder entsorgt werden. Da wäre ein ''.join(['x' for i in range(1000)]) deutlich effektiver. Ja, ich weiß. 'x' * 1000 wäre noch besser, aber es ging ja nur ums Beispiel. Thomas -- I have seen things you lusers would not believe. I've seen Sun monitors on fire off the side of the multimedia lab. I've seen NTU lights glitter in the dark near the Mail Gate. All these things will be lost in time, like the root partition last week.
On 2017-08-29 17:05, Thomas Orgelmacher <trash@odbs.org> wrote:
Was man nicht machen sollte ist eine Konstruktion wie
s = '' for i in range(1000): s = s + 'x'
Hier wird 1001 mal ein str instantiiert und 1000 müssen vom CG wieder entsorgt werden.
Da wäre ein ''.join(['x' for i in range(1000)]) deutlich effektiver. Ja, ich weiß. 'x' * 1000 wäre noch besser, aber es ging ja nur ums Beispiel.
Wenn es Dir nicht ums Beispiel geht, dann hast Du wahrscheinlich nicht 1000 mal den gleichen String sondern 1000 verschiedene Strings. Wenn Du die bereits in einer Liste vorliegen hast, ist "".join(liste) natürlich ideal. Aber wenn Du die Liste erst bauen musst, ist liste = [] for ...: element = ... liste.append(element) s = "".join(liste) sicher nicht besser als s = "" for ...: element = ... s += element (String-Konkatenation ist in Python(3) zumindest bei langen Strings auch schön linear) hp -- _ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung: |_|_) | | Man feilt solange an seinen Text um, bis | | | hjp@hjp.at | die Satzbestandteile des Satzes nicht mehr __/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Peter J. Holzer schrieb am 16.09.2017 um 09:28:
(String-Konkatenation ist in Python(3) zumindest bei langen Strings auch schön linear)
Es gibt sowohl für bytes-Objekte als auch für Unicode-Strings eine Sonderbehandlung, die realloc() verwendet. Auf vielen Platformen ist realloc() effizient und führt im Durchschnitt zu einer linearen Laufzeit auch bei mehreren Konkatenierungen. Aber nicht auf allen. Außerdem greift die Optimierung nicht in allen Fällen. Beispielsweise hast du Pech, wenn über die Konkatenierungen hinweg der Zeichenraum erst von ASCII auf BMP und dann auf Astral wechselt. Dann muss beim Wechsel der komplette bisherige String doch wieder im Speicher herum kopiert werden. Ein join() kann das vermeiden, weil gleich am Anfang alle Strings bekannt sind, und damit auch der finale Zeichenraum. Hier hast du also die Garantie, dass das Zusammenfügen effizient erfolgt. Also kurz: join() ist in jedem Fall die bessere Variante bei vielen Strings. Konkatenierung ist ok bei einer überschaubaren Menge und da insbesondere bei kurzen Strings, sollte aber in Schleifen vermieden werden. Stefan
Am 16.09.2017 um 09:28 schrieb Peter J. Holzer:
Wenn Du die bereits in einer Liste vorliegen hast, ist "".join(liste) natürlich ideal.
Aber wenn Du die Liste erst bauen musst, ist
liste = [] for ...: element = ... liste.append(element) s = "".join(liste)
sicher nicht besser als
s = "" for ...: element = ... s += element
Wenn bei einer Liste ein Element angehängt wird werden vermutlich nur eine 4 oder 8 byte lange Adresse in ein Feld angehängt. Bei jedem Buchstabe je nach utf-darstellung 2 oder 4 byte. ( Bei diakritischen Zeichen entsprechend mehr) Wenn bei Speicherplatz eines Feldes nicht bei jeden neuen Element ein neues Feld angelegt wird, sondern bei Erweiterungen hinten vielleicht 20% Platz für Erweiterungen reserviert werden, dürfte es einiges schneller gehen.
(String-Konkatenation ist in Python(3) zumindest bei langen Strings auch schön linear)
In Python2 war jeder Buchstabe 1 byte; utf- Sonderzeichen wie ö Zeichen waren halt mehrere Buchstaben. ( Diakritische Zeichen sind auch in Python3 "mehr als ein Buchstabe" was z.B. bei Indizierung, Zerlegung etc. unerwartete Effekte haben kann.) Hermann der nicht sagen kann, ob für strings mit gleichem Inhalt jedes mal neuer Speicherplatz angelegt wird, oder eine hash Suche losläuft. -- http://www.hermann-riemann.de
Hermann Riemann schrieb am 16.09.2017 um 16:19:
Wenn bei einer Liste ein Element angehängt wird werden vermutlich nur eine 4 oder 8 byte lange Adresse in ein Feld angehängt. Bei jedem Buchstabe je nach utf-darstellung 2 oder 4 byte. ( Bei diakritischen Zeichen entsprechend mehr)
In Py2 gab es die 2-byte Unicode-Option, die wirklich UTF-16 verwendet hat (war vor allem unter Windows verbreitet), aber in Py3 ist die interne Darstellung nicht UTF-kodiert, sondern es wird zwischen ASCII, ISO8859-1, BMP und Astral unterschieden. In einzelnen Sonderfällen gibt es auch da noch eine UTF-16 Darstellung, aber die wird praktisch (außerhalb der Windows-Welt) nicht mehr verwendet. Kannst sie als Implementierungsdetail ansehen.
Wenn bei Speicherplatz eines Feldes nicht bei jeden neuen Element ein neues Feld angelegt wird, sondern bei Erweiterungen hinten vielleicht 20% Platz für Erweiterungen reserviert werden, dürfte es einiges schneller gehen.
Ist bei Listen so, genau aus dem Grund. Wenn einmal was angehängt wird, war mit mit sehr hoher Wahrscheinlichkeit nicht das letzte Mal.
In Python2 war jeder Buchstabe 1 byte;
Blödsinn.
utf- Sonderzeichen wie ö Zeichen waren halt mehrere Buchstaben.
Blödsinn.
( Diakritische Zeichen sind auch in Python3 "mehr als ein Buchstabe" was z.B. bei Indizierung, Zerlegung etc. unerwartete Effekte haben kann.)
Richtig. Da hilft in den meisten Fällen eine Zeichennormalisierung. Oft genug ist es aber auch einfach egal, weil Text überraschend oft gar nicht zeichenweise verarbeitet wird.
Hermann der nicht sagen kann, ob für strings mit gleichem Inhalt jedes mal neuer Speicherplatz angelegt wird, oder eine hash Suche losläuft.
Code lesen hilft. Stefan
participants (8)
-
Dr. Volker Jaenisch
-
Hermann Riemann
-
Mike Müller
-
Peter J. Holzer
-
Stefan Behnel
-
Stefan Schwarzer
-
Thomas Orgelmacher
-
Tobias Herp