25th Aug
0
String Konkatenation in Python
Das Arbeiten mit Strings wird immer, insbesondere beim Umgang mit Sprache, sehr vielseitig sein und nimmt schnell einen großen Teil des Codes ein. Da allerdings Nutzereingaben meist Strings sind, und damit auch die Rückgabewerte in diesem Datentyp gespeichert werden, gilt es beim Arbeiten mit Strings keine zusätzliche Performance zu verschwenden.
Einen wesentlichen Punkt, das Verknüpfen von Strings (von latein. catena „Kette“), möchte ich deshalb hier kurz beschreiben.
Fünf Möglichkeiten
Intuitiv Zusammensetzen
def method1(): retval = '' for num in range(count): retval += 'num' return retval
Die „backticks“ entsprechen übrigens einem einfachen str(num) zum Umwandeln des Typs. Diese Methode ist jedoch keine Gute. Allerdings merkt man Unterschiede erst ab sehr großen Zahlen. Wenn ihr jedoch eine erfolgreiche Webanwendung betreibt, dann bitte nicht je Programmaufruf rechnen, sondern die Summe aller erwarteten Aufrufe. Für ein n von 1000 merkt man noch sicherlich noch keinen Unterschied in obigem Beispiel zu den folgenden Methoden, jedoch wenn 100 Besucher zur Spitzenzeit der Seite alle diesen Aufruf starten, führt das Programm ganze 100000 Konkatenationen durch. Deshalb immer Abwägen, ob sich ein Aufruf überhaupt lohnt, oder ein Cache nicht bessere Ergebnisse erzielt.
MutableString
def method2(): from UserString import MutableString retval = MutableString() for num in range(count): retval += 'num' return retval
UserString enthält zwei Wrapper Klassen für Strings. Die Klasse UserString dient als Basisklasse um eigene Funktionalität zu Strings durch Vererbung hinzufügen zu können. Die Klasse MutableString ist „educational“ um Vererbung demonstrieren zu können. Für unser Vorhaben ist sie deshalb völlig ungeeignet und auch die langsamste./h5>
Charakter Arrays
def method3(): from array import array char_array = array('c') for num in range(count): char_array.fromstring('num') return char_array.tostring()
Mit dieser Methode sehen wir erstmals einen tatsächlichen Sprung in der Performance. Mit fromstring() fügen wir dem Ende des Arrays den Wert hinzu. Diese Methode ist allerdings vor allem deshalb betrachtenswert, da Arrays in Python „mutable“ sind, das heißt wir können Elemente einfügen ohne Array Inhalte kopieren zu müssen. Das kann sehr sinnvoll sein, wenn das Zusammenstellen der Daten, die zu einem String werden sollen sehr komplex ist.
Puffer
def method4(): from cStringIO import StringIO file = StringIO() for num in range(count): file.write('num') return file.getvalue()
Das Modul cStringIO ist die schnelle Implementierung von StringIO. Hierbei handelt es sich um gepufferte Strings, auch als „memory strings“ bezeichnet. Die Methode ist definitiv ein Anwärter auf den Spitzenplatz. Falls der Puffer wieder verschwinden soll, kann er mittels close() geschlossen werden. Jeder weitere Zugriff erzeugt ab dann einen ValueError. Es gibt übrigens auch eine C API zu diesem Modul.
Listen und Joins
def method5(): list = [] for num in range(count): list.append('num') return ''.join(list)
Jeder Informatiker der jetzt aufschreit, weil Python uns die letzte Zeile tatsächlich durchgehen lässt, kann auch string.join(list, ”) schreiben. Da Listen in Python sehr schnelle Objekte sind und der join fast nichts kostet, konkurriert diese Methode mit dem Puffer und dem Array. Es muss übrigens kein leerer String sein. Der String wird hinter jedem Listenelement verwand, außer hinter dem Letzten. Sehr nützlich um SQL Select Anfragen im Tupelkalkül zu erstellen.
Bei dieser Methode ist sehr entscheiden wie wir die Liste zusammenbauen. Der join akzeptiert z.B.:
def method(): return ''.join(['num' for num in range(count)])
Wenn es eine geeignete Darstellung der Funktionalität als lambda Funktion gibt, kann es dadurch am performantesten programmiert werden.
Zusammenfassung
Methode 5 ist dann die Beste, falls es gelingt die Liste möglichst elegant zu befüllen. Ansonsten kann, reicht eine rein sequentielle Befüllung des Strings, der Puffer punkten. Wenn Ihr sehr umständliche Strings zur Laufzeit basteln und verändern wollt, dann könnt ihr es mit einem Array versuchen.
Ich empfehle unbedingt hier das Konzept des Modultests anzuwenden und Eure Software selbst zu testen (bzw. „Debugging for performance“). Für eine Laufzeitmessung gibt es unter Python die Module time oder timing. Um die Prozessgröße zu bekommen kann man unter Unix mit den Modulen commands und os folgendes versuchen:
process_size = commands.getoutput('ps -up ' + 'os.getpid()').split()[15]
Eine letzte Anmerkung sei mir noch gestattet. Scheinbar ist range schneller als xrange, hat dafür jedoch den höheren Speicherverbrauch. Beim Modultest sollte deshalb ein Test mit xrange und ein Test mit range durchgeführt werden.
About the Author
Computer sollen uns in Beruf und Alltag unterstützen. Damit das funktioniert müssen wir einen einfachen und intuitiven Zugang zu unseren Programmen, Daten und Systemen haben.