Ein häufig wiederkehrendes Problem besteht darin, Zeichenketten zu durchsuchen oder zu manipulieren. Wir haben bereits früher Programme geschrieben, die nach einer Teilzeichenkette suchen oder eine solche ersetzen. Da dieses Problem so häufig auftritt, wird dazu in der Regel ein Mechanismus verwendet, der ohne explizite Programmierung auskommt. Dieser erlaubt es auch, statt konkrete Teilzeichenketten zu suchen oder zu ersetzen, ganze Klassen von Teilzeichenketten anzugeben.
Diese Klasse sind Mengen von Wörtern, also Sprachen, die wir mit Hilfe von (E)BNF beschreiben könnten. Reguläre Ausdrücke sind eine einfachere Methode, Sprachen (also Mengen von Wörtern) zu beschreiben. Sie erlauben effizientere Methoden zur Implementierung von Such- und Ersetzungs-Algorithmen als (E)BNF, sind jedoch auch nicht so ausdrucksstark.1 Es gibt also Sprachen, die mit (E)BNF, nicht aber mit regulären Ausdrücken beschrieben werden können. Die früher beschriebene Sprache für arithmetische Ausdrücke ist zum Beispiel so eine Sprache. Reguläre Ausdrücke sind jedoch ausdrucksstark genug für viele relevante Problemstellungen und werden wegen ihrer vergleichsweisen Einfachheit oft bevorzugt.
Um reguläre Ausdrücke in Python verwenden zu können, muss man zunächst
das Modul re
importieren:
>>> import re
Reguläre Ausdrücke sehen in Python genau wie Strings aus, man schreibt
aber meist ein kleines r
vor das öffnende Anführungszeichen, um
anzuzeigen, dass dieser String ein regulärer Ausdruck ist.2
Im einfachsten Fall bestehen Sie einfach aus
einer Zeichenkette wie zum Beispiel r'elf'
.
Möchte man nun einen regulären Ausdruck gegen einen String matchen, so
kann man z. B. die Funktion match
aus dem Modul re
verwenden:
>>> m = re.match(r'elf','elfen')
>>> print(m)
<re.Match object; span=(0, 3), match='elf'>
Wir sehen also, dass Python ein Match-Objekt anlegt, welches alle relevanten
Informationen zu dem erfolgreichen Match beinhaltet. Dies ist insbesondere
der span
, welcher ein Paar bestehend aus Anfangsposition des Matches und
der Position des Zeichens hinter dem Match ist. Darüber hinaus noch das
Wort, welches gematcht wurde. Diese beiden Komponenten können mit Hilfe der
Methoden span()
und group()
aus einem Match-Objekt ausgelesen werden.
Wir haben die Methode match
verwendet, welche nur dann erfolgreich ist, wenn
der reguläre Ausdruck am Anfang des Strings matcht:
>>> m = re.match(r'elf','Spielfigur')
>>> print(m)
None
Obwohl im Wort 'Spielfigur'
ein 'elf'
steckt, ist das Matching nicht
erfolgreich, was durch das Ergebnis None
anstelle eines Match-Objekts
angezeigt wird.
Möchte man schauen, ob ein regulärer Ausdruck an irgendeiner Postion im
String matcht, verwendet man die Methode search
, welche das erste (am
(weitesten links stehende) vorkommende Matching zurückgibt:
>>> m = re.search(r'elf','Spielfigur')
>>> print(m)
<re.Match object; span=(3, 6), match='elf'>
Mit Hilfe von span()
und group
kann man wieder alle relevanten
Informationen zu dem gefundenen Match extrahieren.
Bisher haben wir nur einfach Zeichenfolgen (Buchstaben-Sequenzen) als reguläre Ausdrücke verwendet. Oft möchte man aber auch bestimmte Komponenten des Strings variabel lassen. Hierzu gibt es diverse weitere Konstrukte im regulären Ausdruck, welche wir im Folgenden anhand von Beispielen kennenlernen werden.
Hierfür werden bestimmte Zeichen in regulären Ausdrücken nicht als solche interpretiert, sondern haben eine besondere Bedeutung. Zum Beispiel steht der Punkt für ein beliebiges Zeichen und nicht für einen Punkt. Auch ist es möglich, explizit Gruppen von Zeichen zu definieren und auch Gruppen, die alle nicht genannten Zeichen beschreiben.
>>> re.search(r'e.f', 'Spielfigur')
<re.Match object; span=(3, 6), match='elf'>
>>> re.search(r'e[a-z]f', 'Spielfigur') # ein Kleinbuchstabe
<re.Match object; span=(3, 6), match='elf'>
>>> re.search(r'e[^A-Z]f', 'Spielfigur') # kein Großbuchstabe
<re.Match object; span=(3, 6), match='elf'>
Für bestimmte Gruppen sind Sonderzeichen vordefiniert:
\d
steht für eine beliebige Ziffer, also [0-9]
,
\w
steht für ein Wortzeichen, also [a-zA-Z0-9]
und
\s
steht für ein beliebiges Leerzeichen (inklusive Tabulatoren
und Zeilenumbrüchen).
Das Fragezeichen kennzeichnet ein optionales Zeichen:
>>> re.search(r'elfi?', 'Spielfigur')
<re.Match object; span=(3, 7), match='elfi'>
>>> re.search(r'elfe?', 'Spielfigur')
<re.Match object; span=(3, 6), match='elf'>
Durch Klammerung kann es auch auf mehrere Zeichen angewendet werden:
>>> re.search(r'elf(en)?', 'Spielfigur')
<re.Match object; span=(3, 6), match='elf'>
Durch einen senkrechten Strich werden (wie bei der BNF) Alternativen gekennzeichnet:
>>> m = re.search(r'i(e|g)', 'Spielfigur')
>>> print(m)
<re.Match object; span=(2, 4), match='ie'>
>>> m.group()
'ie'
>>> m.group(1)
'e'
Dieses Beispiel zeigt, dass immer der am weitesten links stehende mögliche Teilstring
gematcht wird. Außerdem ist es möglich, auf die Matches der geklammerten Teile des
regulären Ausdrucks mit der Methode group(n)
zuzugreifen. Hierbei
gibt der Parameter n
an, um welches Klammerpaar im regulären
Ausdruck es sich handelt (von 1 an gezählt). Der Aufruf der Methode group()
stellt eine Abkürzung für den Aufruf group(0)
dar, welcher den gesamten
gematchten String liefert.
>>> m = re.match(r'(.....)((..)...)', 'Spielfigur')
>>> m.group()
'Spielfigur'
>>> m.group(1)
'Spiel'
>>> m.group(2)
'figur'
>>> m.group(3)
'fi'
Schließlich gibt es noch Möglichkeiten, Wiederholungen zu spezifizieren. Der Stern kennzeichnet eine optionale Wiederholung und das Plus-Zeichen eine mindestens einmal wiederholte Zeichenfolge.
>>> m = re.search(r'pi.*g', 'Spielfigur')
>>> m
<re.Match object; span=(1, 8), match='pielfig'>
>>> m = re.search(r'pi.+g', 'Spielfigur')
>>> m
<re.Match object; span=(1, 8), match='pielfig'>
>>> m = re.search(r'pi.*e', 'Spielfigur')
>>> m
<re.Match object; span=(1, 4), match='pie'>
>>> m = re.search(r'pi.+e', 'Spielfigur')
>>> print(m)
None
Statt Teilzeichenketten zu suchen, können wir diese auch ersetzen. Die
Methode replace
auf Zeichenketten ersetzt das erste Vorkommen einer
Zeichenkette durch eine andere Zeichenkette:
>>> 'abc'.replace('b','x')
'axc'
>>> 'abc'.replace('bc','x')
'ax'
Entsprechend ersetzt die Methode sub
alle Matchings eines regulären Ausdrucks
durch einen vorgegebenen String:
>>> re.sub(r'i.+g', 'x', 'Spielfigur')
'Spxur'
>>> re.sub(r'i.', 'x', 'Spielfigur')
'Spxlfxur'
Entsprechend ist es mit der Methode findall
auch möglich, alle
Matchings eines regulären Ausdrucks in einem String zu finden.
Hierbei wird aber nur eine Liste aller gematcheten Sub-Strings
zurückgegeben, keine Match-Objekte:
>>> re.findall(r'i.', 'Spielfigur')
['ie', 'ig']
Falls der reguläre Ausdruck an keiner Stelle matcht, wird die leere Liste zurückgegeben:
>>> re.findall(r'ia', 'Spielfigur')
[]