Python-Programmierung: Zeichenketten

Aus Wikibooks

Wechseln zu: Navigation, Suche

Inhaltsverzeichnis

[Bearbeiten] Allgemeines

Zeichenketten in Python sind unveränderbare (immutable) Sequenzen, auch Strings genannt. Ihr Typ ist:

>>> type("Ich bin ein String")
<type 'str'>

Mit anderen Worten, Strings sind Stringobjekte.

[Bearbeiten] Stringliterale

Es gibt drei einfache Varianten, um Zeichenketten zu bilden:

  • Durch einfache Apostrophe,
  • doppelte ("Gänsefüßchen"),
  • oder dreifache Anführungszeichen.

Apostroph und doppelte Anführungsstriche sind äquivalent und bezeichnen ein shortstringitem. Zeichenketten in dreifachen Anführungszeichen können sich über mehrere Zeilen erstrecken und nennen sich longstringitem. Als Begrenzung können wieder sowohl dreifache Apostrophe als auch dreifache Anführungszeichen benutzt werden.

>>> 'abc'
'abc'
>>> "die Katze"
'die Katze'
>>> """lief
... im Schnee"""
'lief\nim Schnee'

In der letzten Zeile wurde eine sogenannte Escape-Sequenz, wie sie auch aus anderen Sprachen bekannt ist, eingefügt. In diesem Fall handelt es sich um einen Zeilenvorschub (\n).

In Strings dürfen die entsprechenden Begrenzer nicht vorkommen, da Python sonst nicht wissen kann wo ein String anfängt bzw. aufhört. Es ist aber ohne weiteres möglich den jeweils anderen Begrenzer zur Begrenzung zu verwenden. Also z.B.:

>>> 'in Andreas' Haus'  # gibt einen Fehler aus
SyntaxError: invalid syntax
>>> "in Andreas' Haus"  # kein Problem
"in Andreas' Haus"

[Bearbeiten] Steuerzeichen

Mit Steuerzeichen kan man Tabulatoren und Zeilenumbrüche und Ähnliches mit einer speziellen Schreibweise angeben, die der Programmiersprache C entlehnt ist. Einige dieser Zeichen sind eher von historischer Bedeutung, da sie urprünglich zur Steuerung von Terminals dienten.

\Zeilenende Kein Effekt
\\ Schrägstrich rückwärts
\a Steuerzeichen BEL (Tonsignal)
\b Rückwärtsschritt (1 Zeichen)
\f Seitenende
\n Zeilenvorschub
\r Rücklauf zum Zeilenanfang
\t (Horizontaler) Tabulator
\v Vertikaler Tabulator

[Bearbeiten] Sonderzeichen

Je nach landessprachlicher Einstellung können Umlaute verwendet werden:

>>> "öäü"
'\xc3\xb6\xc3\xa4\xc3\xbc'
>>> print "öäü"
öäü

Im interaktiven Modus werden Ergebnisse sofort ausgegeben, ohne daß dazu eine print-Anweisung nötig ist. Allerdings unterscheidet sich das sichtbare Ergebnis je nach Ausgabemethode. Mit print werden lesbare Zeichen angezeigt, während im Taschenrechner-Modus Sonderzeichen im Wert der aktuellen Codierung ausgegeben werden (im Beispiel oben in UTF-8).

Um beliebige Zeichen darstellen zu können, können Ordnungszahlen von Zeichen in Hexadezimal- oder Oktal-Schreibweise benutzt werden, z.B in UTF-8:

>>> "\xc3\xb6\xc3\xa4\xc3\xbc" # Hexadezimal
öäü
>>> "\303\244" # Oktal
ä

Dummerweise ist die Codierung vom jeweils eingestellten Zeichensatz abhängig, weswegen Programme, die Zeichen auf diese Art codieren, nicht portabel sind. Eine bessere Lösung sind Unicode-Strings, die im nächsten Abschnitt erklärt werden.


[Bearbeiten] String-Varianten

Einfache Strings sind für die allermeisten Anwendungsfälle ausreichend. Probleme treten meist erst dann auf, wenn Programme auf verschiedene Landessprachen angepaßt werden sollen, oder wenn Reguläre Ausdrücke ins Spiel kommen. Hierfür gibt es spezielle Formen von Strings.

Ein Unicode-String kann sämtliche auf einem Computer darstellbaren Zeichen enthalten. Es werden beliebige Schriftarten unterstützt, insbesondere auch nicht-lateinische z.B. Kyrillisch, Arabisch oder Chinesisch.

Unicode-Strings werden durch ein führendes U bzw. u gekennzeichnet. In Ihnen können beliebige Zeichen als Hexadezimal-Code angegeben werden, der durch die Zeichen \u eingeleitet wird. Mit der Funktion unicode lassen sich normale Zeichenketten nach Unicode umwandeln, wobei die Codierung des Originals angegeben werden sollte.

>>> U"\u00e4"
u'\xe4'
>>> unicode( 'öäü', 'UTF-8')
u'\xf6\xe4\xfc'

Wenn die Auswertung von Steuerzeichen in einer Zeichenkette nicht gewünscht wird, kann man diese mit einem führenden r oder R deaktivieren. Dies ist meist bei Regulären Ausdrücken sinnvoll. Es kann auch in Kombination mit Unicode-Strings eingesetzt werden.

>>> "\r\n" # Normaler String
'\r\n'
>>> r"\r\n" # Ohne Auswertung von Steuerzeichen
'\\r\\n'
 
>>> u"\r\n" # Unicode-String
u'\r\n'
>>> ur"\r\n"      # Ohne Auswertung von Steuerzeichen
u'\\r\\n'

[Bearbeiten] Stringmethoden

Strings, wie alles andere auch, sind Objekte und verfügen über eine ganze Reihe von Methoden und Operationen, mit denen sich manipulieren lassen.

[Bearbeiten] Strings als Sequence Type

Strings gehören, wie auch Listen, Tuple und noch ein paar andere Objektklassen, zur Superklasse der sequence types, also der aufzählbaren Typen und erbt damit eine ganze Reihe praktischer Funktionen und Methoden.

>>> a = "Ich bin ein String" # Ein String
>>> len(a) # Strings haben eine Länge
18
>>> a[0] # Einzelne Zeichen lassen sich indizieren
'I'
>>> a[-6] # Bei negativen Index wird vom Ende gezählt
'S'
>>> a[4:11] # Bereiche lassen sich ausschneiden
'bin ein'
>>> a[:3] # entspricht a[0:3]
'Ich'
>>> a[:-6] 
'Ich bin ein '
>>> a[-6:]
'String'

Gerade der Python slicing-Operator ([:]) ist sehr leistungsfähig und universell einsetzbar. Was er nicht kann, ist, einen String in place zu ändern:

>>> a[5] = 'X'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object does not support item assignment

Python Zeichenketten sind immutable sequences, die kein 'in-place-edting zulassen. Man muss sich Strings daher als Read-Only-Objekte vorstellen. Bei einer Anweisung, wie a = a[1:], die in ihrer Wirkung das erste Zeichen von einem String entfernt, konstruiert der slicing-Operator ein neues Stringobjekt. Dessen Referenz ersetzt dann die bisherige mit dem Namen a verbundene Referenz.

[Bearbeiten] String-Operationen

Fahren wir mit unserer Pythonsitzung noch ein wenig fort:

>>> b = "Hallo Welt"
>>> c = a + ". " + b
>>> c
'Ich bin ein String. Hallo Welt'
>>> c += "! Hallo Python"
>>> c
'Ich bin ein String. Hallo Welt! Hallo Python'

Strings lassen sich mit dem Pluszeichen zusammenfügen (concat). Stringliterale sogar ohne:

>>> "abc" "def"
'abcdef'

So praktisch diese Operationen auch sind, darf man niemals vergessen, jedes Mal ein neuer String konstruiert werden muss, was zum Performancekiller werden kann.

[Bearbeiten] Seitenbetrachtung: Warum Stringkonkatination schlecht ist

Der Plus-Operator bei Strings gilt in Python inzwischen als echtes anti pattern - Programmiermuster, die man vermeiden sollte. Warum?

Schauen wir uns dass nachfolgende, zugegeben nicht sehr sinnvolle, Programm einmal an, dass eine Zeichenkette der ersten eine 100.000 Quadratzahlen erzeugt und in umgekehrter Reihenfolge ausgibt.

a = ""
 
for x in xrange(100000):
    a = "%ld, " % (x * x) + a
 
print a

Auf den ersten Blick, sieht der Code ganz harmlos aus. Mit jedem Durchlauf der Schleife, scheint eine Quadratzahl als Zeichenkette an die bestehende angehängt zu werden. Ist dem wirklich so?

Nun, wenn so gefragt wird, lautet die Antwort erwartungsgemäß nein. Diese Schleife, so richtig und plausibel sie erscheint, ist ein grauenvoller CPU-Zyklen und Speicherfresser. Doch der Reihe nach. Spielen wir den Ablauf einmal durch.

Am Anfang erzeugt die Anweisung a = "" ein leeres Zeichenkettenobjekt, dessen Referenz im Namen a abgelegt wird, danach betreten wir das erste mal die Schleife. x erhält den Wert 0 und der Ausdruck "%ld" % (x * x) erzeugt eine Zeichenkette "0". Jetzt wird es spannend: Würde man den Zwischenschritt niederschreiben, stände jetzt der Code a = "0" + a zur Ausführung an. Der nächste Schritt besteht in der Konkatenation von "0" und a oder aufgelöst "0" und "".

Zeichenketten sind unveränderbare (immutable) Pythonobjekte, d.h. statt, wie man vielleicht erwarten würde, das an die "0" die leere Zeichenkette anzufügen, konstruiert der +-Operator ein völlig neues Stringobjekt, dessen Referenz anschließend an den Variablennamen a gebunden wird. Die alte Stringreferenz in a (die leere Zeichenkette) wird freigegeben (garbage collection).

Dieser Ablauf wiederholt sich eine Million mal! Eine Million mal wird ein neuer String konstruiert und ein der alte freigegeben. Aber nicht nur dass, jedes Mal muss der Inhalt des alten Strings in den neuen kopiert werden. Im zehnten Durchlauf, sind dies schon 41 Zeichen, im 100 schon 561, bei 1000 schon 7546, bei 10.000 müssen bereits rund 95 KiB kopiert werden. Ab dem 91.235 Durchlauf müssen mehr als 1 MiB pro weiteren Durchlauf kopiert werden. Im letzten Durchlauf wurden 12,91 MiB kopiert. Klingt nicht sonderlich effizent, oder? Es dürfte daher auch nicht wundern, dass dieses Codeschnippsel knapp 82,1 Sekunden auf meinem Rechner benötigte. Für 10.000 Durchläufe, benötigt das Programm hingegen nur 0,3 Sekunden. So harmlos das Programm wirkt, er besitzt einen quadratischen Aufwand, dass heißt O(n) = n2.

Es geht aber auch anders:

a = []
 
for x in xrange(1000000):
    a.append("%ld" % (x * x))
 
a = ', '.join(reversed(a))
print a

Diese Variante benötigte nur 2,7 Sekunden, allerdings für eine Million Durchläufe, für die 100.000 benötigt es nur 0,2 Sekunden. Mit der gesunkenen Laufzeit, ist auch der Speicherverbrauch drastisch gesunken. Statt pro Durchlauf, Megabytes im Speicher hin und her zu schaufeln, werden in dieser Version pro Durchlauf nur Zeichenketten mit maximal sechs Zeichen erzeugt und an das Listenobjekt a angehängt, was eine sehr preiswerte Operation ist. Für zeitkritische Anwendungen sollten daher immer zweite Variante verwendet werden.

Natürlich ist auch den Pythonentwicklern bekannt, dass Stringkonkatenationen sehr beliebt sind und haben bestimmte Varianten so optimiert, dass sie vom Interpreter erkannt werden und eine in-place Manipulation der Zeichenkette vornehmen. Hier ein Beispiel:

t = ""
 
for x in xrange(1000000):
    t += "%ld, " % (x * x)
 
print t

Dieses Progrämmchen gibt die Quadratzahlen aufsteigend aus, benötigt für eine Million Durchläufe aber nur 2,9 Sekunden und liegt damit der Listen-Variante (append()) etwa gleich auf. Erkannt und optimiert werden die beiden gleichwertigen Formulierungen t += a und t = t + a, wenn t und a Zeichenketten sind.

[Bearbeiten] String-Methoden

Pythonzeichenketten verfügen über eine ganze Reihe leistungsfähiger Methoden, wovon im nachfolgenden Beispiel ein paar gezeigt werden:

>>> a = "Ich bin ein String"
>>> a.upper() # Alles in Großbuchstaben
'ICH BIN EIN STRING'
>>> a.lower() # Oder auch in Kleinbuchstaben
'ich bin ein string'
>>> a.split() # An Leerzeichen teilen
['Ich', 'bin', 'ein', 'String']
>>> a.find("String") # Eine Zeichenkette in einer Zeichenkette suchen
12
>>> a.count("in") # Die Häufigkeit des Auftretens einer Zeichenkette 
3
>>> b = "   Viel Luft um Nichts!    "
>>> b.strip() # Vorne und hinten allen Leerraum (whitespace) abschneiden
'Viel Luft um Nichts!'

split ist eine sehr praktische Methode, mit der sich Strings anhand von Trennzeichen in eine Liste zerlegen lassen. Hier ein Beispiel der Zerlegung einer Zeile der /etc/passwd eines Linux-Systems:

>>> passwdLine = "at:x:25:25:Batch jobs daemon:/var/spool/atjobs:/bin/bash"
>>> passwdLine.split(":")
['at', 'x', '25', '25', 'Batch jobs daemon', '/var/spool/atjobs', '/bin/bash']

Ohne Argument spaltet split eine Zeichenkette an Leerräumen (whitespace, also Space, Tab und Zeilenvorschub).

Ähnlich praktisch ist strip, mit der man überschüssigen Leeraum am Anfang oder Ende einer Zeichenkette entfernen kann, z.B. bei Auslesen eines Eingabgefelds einer GUI.

Es lohnt sich gelegentlich einen Blick in die Python-Library-Referenz zu werfen. Viele Alltagsaufgaben im Umgang mit Zeichenketten lassen sich direkt mit den Methoden dieses Objekttyps lösen.

<< Inhaltsverzeichnis

Persönliche Werkzeuge