HOWTO zu regulären Ausdrücken¶
Einführung¶
Reguläre Ausdrücke (kurz REs, Regexes oder Regex-Muster) sind im Kern eine kleine, hochspezialisierte Programmiersprache, die in Python eingebettet ist und über das Modul re bereitsteht. Mit dieser kleinen Sprache gibst du die Regeln für die Menge der möglichen Zeichenketten an, auf die du passen willst. Diese Menge kann englische Sätze, E-Mail-Adressen, TeX-Befehle oder beliebiges anderes enthalten. Anschließend kannst du Fragen stellen wie „Passt diese Zeichenkette auf das Muster?“ oder „Gibt es irgendwo in dieser Zeichenkette eine Fundstelle für das Muster?“. Du kannst REs auch verwenden, um eine Zeichenkette zu verändern oder auf verschiedene Weisen zu zerlegen.
Muster regulärer Ausdrücke werden in eine Folge von Bytecodes übersetzt, die dann von einer in C geschriebenen Suchmaschine ausgeführt werden. Für fortgeschrittene Zwecke kann es nötig sein, genau darauf zu achten, wie die Maschine einen bestimmten RE ausführt, und den RE so zu schreiben, dass schnellerer Bytecode entsteht. Optimierung behandelt dieses Dokument nicht, weil sie ein gutes Verständnis der Interna der Suchmaschine voraussetzt.
Die Sprache der regulären Ausdrücke ist relativ klein und eingeschränkt, deshalb lassen sich nicht alle denkbaren Aufgaben der Zeichenkettenverarbeitung mit regulären Ausdrücken lösen. Es gibt außerdem Aufgaben, die sich mit regulären Ausdrücken lösen lassen, bei denen die Ausdrücke aber sehr kompliziert werden. In diesen Fällen fährst du möglicherweise besser damit, die Verarbeitung in Python-Code zu schreiben; Python-Code ist zwar langsamer als ein ausgefeilter regulärer Ausdruck, dafür aber wahrscheinlich verständlicher.
Einfache Muster¶
Wir fangen mit den einfachsten möglichen regulären Ausdrücken an. Da reguläre Ausdrücke auf Zeichenketten angewendet werden, beginnen wir mit der häufigsten Aufgabe: dem Passen auf Zeichen.
Eine ausführliche Erklärung der informatischen Grundlagen regulärer Ausdrücke (deterministische und nichtdeterministische endliche Automaten) findest du in nahezu jedem Lehrbuch über das Schreiben von Compilern.
Auf Zeichen passen¶
Die meisten Buchstaben und Zeichen passen einfach auf sich selbst. Der reguläre Ausdruck test passt zum Beispiel genau auf die Zeichenkette test. (Du kannst einen Modus einschalten, der Groß- und Kleinschreibung ignoriert und bei dem dieser RE auch auf Test oder TEST passt. Dazu später mehr.)
Von dieser Regel gibt es Ausnahmen. Manche Zeichen sind besondere Metazeichen und passen nicht auf sich selbst. Stattdessen zeigen sie an, dass etwas Außergewöhnliches passen soll, oder sie beeinflussen andere Teile des RE, indem sie sie wiederholen oder ihre Bedeutung ändern. Ein großer Teil dieses Dokuments widmet sich den verschiedenen Metazeichen und ihrer Wirkung.
Hier eine vollständige Liste der Metazeichen. Ihre Bedeutungen werden im weiteren Verlauf dieses HOWTO besprochen.
. ^ $ * + ? { } [ ] \ | ( )
Die ersten Metazeichen, die wir uns ansehen, sind [ und ]. Mit ihnen gibst du eine Zeichenklasse an, also eine Menge von Zeichen, auf die du passen willst. Die Zeichen lassen sich einzeln aufführen, oder du gibst einen Zeichenbereich an, indem du zwei Zeichen durch ein '-' trennst. [abc] passt zum Beispiel auf jedes der Zeichen a, b oder c. Das ist dasselbe wie [a-c], das dieselbe Zeichenmenge über einen Bereich ausdrückt. Willst du nur auf Kleinbuchstaben passen, lautet dein RE [a-z].
Metazeichen (außer \) sind innerhalb von Klassen nicht aktiv. [akm$] passt zum Beispiel auf jedes der Zeichen 'a', 'k', 'm' oder '$'; '$' ist normalerweise ein Metazeichen, innerhalb einer Zeichenklasse verliert es aber seine besondere Bedeutung.
Du kannst auf die Zeichen passen, die nicht in der Klasse aufgeführt sind, indem du die Menge komplementierst. Dazu nimmst du ein '^' als erstes Zeichen der Klasse. [^5] passt zum Beispiel auf jedes Zeichen außer '5'. Steht das Caret an anderer Stelle in einer Zeichenklasse, hat es keine besondere Bedeutung. Beispiel: [5^] passt entweder auf eine '5' oder auf ein '^'.
Das vielleicht wichtigste Metazeichen ist der Backslash, \. Wie in Python-Zeichenkettenliteralen kann auf den Backslash eine Reihe verschiedener Zeichen folgen, die verschiedene besondere Sequenzen anzeigen. Er dient außerdem dazu, alle Metazeichen zu maskieren, damit du in Mustern weiterhin auf sie passen kannst. Musst du zum Beispiel auf ein [ oder \ passen, kannst du einen Backslash davorsetzen, um ihre besondere Bedeutung zu entfernen: \[ oder \\.
Einige der besonderen Sequenzen, die mit '\' beginnen, stehen für vordefinierte Zeichenmengen, die oft nützlich sind, etwa die Menge der Ziffern, die Menge der Buchstaben oder die Menge all dessen, was kein Leerraum ist.
Nehmen wir ein Beispiel: \w passt auf jedes alphanumerische Zeichen. Ist das Regex-Muster als Bytes ausgedrückt, entspricht das der Klasse [a-zA-Z0-9_]. Ist das Regex-Muster eine Zeichenkette, passt \w auf alle Zeichen, die in der Unicode-Datenbank des Moduls unicodedata als Buchstaben markiert sind. Die stärker eingeschränkte Bedeutung von \w kannst du in einem Zeichenkettenmuster erreichen, indem du beim Übersetzen des regulären Ausdrucks das Flag re.ASCII angibst.
Die folgende Liste besonderer Sequenzen ist nicht vollständig. Eine vollständige Liste der Sequenzen und die erweiterten Klassendefinitionen für Unicode-Zeichenkettenmuster findest du im letzten Teil von Syntax regulärer Ausdrücke in der Referenz der Standardbibliothek. Allgemein passen die Unicode-Varianten auf jedes Zeichen, das in der Unicode-Datenbank zur passenden Kategorie gehört.
\dPasst auf jede Dezimalziffer. Das entspricht der Klasse
[0-9].\DPasst auf jedes Zeichen, das keine Ziffer ist. Das entspricht der Klasse
[^0-9].\sPasst auf jedes Leerraumzeichen. Das entspricht der Klasse
[ \t\n\r\f\v].\SPasst auf jedes Zeichen, das kein Leerraum ist. Das entspricht der Klasse
[^ \t\n\r\f\v].\wPasst auf jedes alphanumerische Zeichen. Das entspricht der Klasse
[a-zA-Z0-9_].\WPasst auf jedes Zeichen, das nicht alphanumerisch ist. Das entspricht der Klasse
[^a-zA-Z0-9_].
Diese Sequenzen lassen sich in eine Zeichenklasse aufnehmen. [\s,.] ist zum Beispiel eine Zeichenklasse, die auf jedes Leerraumzeichen sowie auf ',' oder '.' passt.
Das letzte Metazeichen in diesem Abschnitt ist .. Es passt auf alles außer einem Zeilenumbruchzeichen, und es gibt einen abweichenden Modus (re.DOTALL), in dem es sogar auf einen Zeilenumbruch passt. . wird oft dort verwendet, wo man auf „ein beliebiges Zeichen“ passen will.
Dinge wiederholen¶
Auf unterschiedliche Zeichenmengen passen zu können, ist das Erste, was reguläre Ausdrücke leisten und was mit den Methoden von Zeichenketten nicht schon möglich wäre. Wäre das allerdings die einzige zusätzliche Fähigkeit von Regexes, wären sie kein großer Fortschritt. Eine weitere Fähigkeit ist, dass du angeben kannst, dass Teile des RE eine bestimmte Anzahl von Malen wiederholt werden müssen.
Das erste Metazeichen zum Wiederholen, das wir uns ansehen, ist *. * passt nicht auf das wörtliche Zeichen '*'. Es gibt stattdessen an, dass das vorangehende Zeichen null oder mehr Mal passen darf statt genau einmal.
ca*t passt zum Beispiel auf 'ct' (0 Zeichen 'a'), 'cat' (1 'a'), 'caaat' (3 Zeichen 'a') und so weiter.
Wiederholungen wie * sind gierig. Beim Wiederholen eines RE versucht die Suchmaschine, ihn so oft wie möglich zu wiederholen. Passen spätere Teile des Musters dann nicht, geht die Suchmaschine zurück und versucht es erneut mit weniger Wiederholungen.
Ein Beispiel Schritt für Schritt macht das deutlicher. Betrachten wir den Ausdruck a[bcd]*b. Er passt auf den Buchstaben 'a', null oder mehr Buchstaben aus der Klasse [bcd] und schließt schließlich mit einem 'b' ab. Stell dir nun vor, dieser RE wird gegen die Zeichenkette 'abcbd' geprüft.
Schritt |
Gefunden |
Erklärung |
|---|---|---|
1 |
|
Das |
2 |
|
Die Maschine lässt |
3 |
Fehlschlag |
Die Maschine versucht, |
4 |
|
Zurückgehen, sodass |
5 |
Fehlschlag |
|
6 |
|
Erneut zurückgehen, sodass |
7 |
|
|
Das Ende des RE ist nun erreicht, und er hat auf 'abcb' gepasst. Das zeigt, wie die Suchmaschine zuerst so weit geht, wie sie kann, und dann, wenn keine Fundstelle zustande kommt, schrittweise zurückgeht und den restlichen RE immer wieder neu versucht. Sie geht zurück, bis sie null Fundstellen für [bcd]* versucht hat, und schließt, wenn auch das fehlschlägt, dass die Zeichenkette überhaupt nicht auf den RE passt.
Ein weiteres Metazeichen zum Wiederholen ist +, das ein oder mehr Mal passt. Achte genau auf den Unterschied zwischen * und +; * passt null oder mehr Mal, das Wiederholte muss also gar nicht vorhanden sein, während + mindestens ein Vorkommen verlangt. Um beim ähnlichen Beispiel zu bleiben: ca+t passt auf 'cat' (1 'a') und 'caaat' (3 'a's), aber nicht auf 'ct'.
Es gibt zwei weitere Wiederholungsoperatoren oder Quantoren. Das Fragezeichen, ?, passt entweder einmal oder null Mal. Du kannst es als Markierung dafür verstehen, dass etwas optional ist. home-?brew passt zum Beispiel entweder auf 'homebrew' oder auf 'home-brew'.
Der komplizierteste Quantor ist {m,n}, wobei m und n Dezimalzahlen sind. Dieser Quantor bedeutet, dass es mindestens m und höchstens n Wiederholungen geben muss. a/{1,3}b passt zum Beispiel auf 'a/b', 'a//b' und 'a///b'. Es passt nicht auf 'ab', das keine Schrägstriche hat, und nicht auf 'a////b', das vier hat.
Du kannst entweder m oder n weglassen. In diesem Fall wird für den fehlenden Wert ein sinnvoller Wert angenommen. Ein fehlendes m wird als Untergrenze 0 verstanden, ein fehlendes n führt zu einer Obergrenze von unendlich.
Der einfachste Fall {m} passt genau m Mal auf das vorangehende Element. a/{2}b passt zum Beispiel nur auf 'a//b'.
Wer zur Reduktion neigt, merkt vielleicht, dass sich die drei anderen Quantoren alle mit dieser Schreibweise ausdrücken lassen. {0,} ist dasselbe wie *, {1,} entspricht +, und {0,1} ist dasselbe wie ?. Es ist besser, *, + oder ? zu verwenden, wo es möglich ist, einfach weil sie kürzer und leichter zu lesen sind.
Reguläre Ausdrücke verwenden¶
Nachdem wir uns einige einfache reguläre Ausdrücke angesehen haben: Wie verwendet man sie nun tatsächlich in Python? Das Modul re bietet eine Schnittstelle zur Maschine für reguläre Ausdrücke, mit der du REs zu Objekten übersetzen und damit dann Suchen durchführen kannst.
Reguläre Ausdrücke übersetzen¶
Reguläre Ausdrücke werden zu Musterobjekten übersetzt, die Methoden für verschiedene Operationen besitzen, etwa das Suchen nach Fundstellen eines Musters oder das Ersetzen in Zeichenketten.
>>> import re
>>> p = re.compile('ab*')
>>> p
re.compile('ab*')
re.compile() nimmt außerdem ein optionales Argument flags, mit dem sich verschiedene besondere Eigenschaften und Syntaxvarianten einschalten lassen. Die verfügbaren Einstellungen gehen wir später durch, für den Moment genügt ein einzelnes Beispiel:
>>> p = re.compile('ab*', re.IGNORECASE)
Der RE wird re.compile() als Zeichenkette übergeben. REs werden als Zeichenketten behandelt, weil reguläre Ausdrücke nicht Teil der Kernsprache Python sind und keine besondere Syntax für ihre Formulierung geschaffen wurde. (Es gibt Anwendungen, die überhaupt keine REs brauchen, also gibt es keinen Grund, die Sprachspezifikation damit aufzublähen.) Das Modul re ist stattdessen einfach ein C-Erweiterungsmodul, das mit Python ausgeliefert wird, genau wie die Module socket oder zlib.
REs in Zeichenketten zu stecken hält die Sprache Python einfacher, hat aber einen Nachteil, der Thema des nächsten Abschnitts ist.
Die Backslash-Plage¶
Wie bereits erwähnt, verwenden reguläre Ausdrücke das Backslash-Zeichen ('\'), um besondere Formen anzuzeigen oder besondere Zeichen ohne ihre besondere Bedeutung verwendbar zu machen. Das kollidiert damit, dass Python dasselbe Zeichen zu demselben Zweck in Zeichenkettenliteralen verwendet.
Nehmen wir an, du willst einen RE schreiben, der auf die Zeichenkette \section passt, wie sie in einer LaTeX-Datei vorkommen kann. Um herauszufinden, was im Programmcode stehen muss, fang mit der gewünschten Zeichenkette an, auf die gepasst werden soll. Als Nächstes musst du alle Backslashes und anderen Metazeichen maskieren, indem du einen Backslash davorsetzt. Das ergibt die Zeichenkette \\section. Die daraus entstehende Zeichenkette, die an re.compile() übergeben werden muss, lautet also \\section. Um das jedoch als Python-Zeichenkettenliteral auszudrücken, müssen beide Backslashes noch einmal maskiert werden.
Zeichen |
Stufe |
|---|---|
|
Textzeichenkette, auf die gepasst werden soll |
|
Maskierter Backslash für |
|
Maskierte Backslashes für ein Zeichenkettenliteral |
Kurz gesagt: Um auf einen wörtlichen Backslash zu passen, muss man '\\\\' als RE-Zeichenkette schreiben, denn der reguläre Ausdruck muss \\ lauten, und jeder Backslash muss innerhalb eines gewöhnlichen Python-Zeichenkettenliterals als \\ ausgedrückt werden. In REs, in denen Backslashes immer wieder vorkommen, führt das zu einer Menge wiederholter Backslashes und macht die entstehenden Zeichenketten schwer verständlich.
Die Lösung besteht darin, für reguläre Ausdrücke Pythons Raw-String-Schreibweise zu verwenden. In einem Zeichenkettenliteral mit dem Präfix 'r' werden Backslashes nicht besonders behandelt, r"\n" ist also eine Zeichenkette aus zwei Zeichen, die '\' und 'n' enthält, während "\n" eine Zeichenkette aus einem Zeichen ist, die einen Zeilenumbruch enthält. Reguläre Ausdrücke werden in Python-Code häufig in dieser Raw-String-Schreibweise geschrieben.
Darüber hinaus führen besondere Escape-Sequenzen, die in regulären Ausdrücken gültig sind, als Python-Zeichenkettenliterale aber nicht, jetzt zu einer SyntaxWarning und werden später zu einem SyntaxError. Die Sequenzen sind damit ungültig, wenn nicht die Raw-String-Schreibweise verwendet oder die Backslashes maskiert werden.
Gewöhnliche Zeichenkette |
Raw-String |
|---|---|
|
|
|
|
|
|
Suchen durchführen¶
Du hast nun ein Objekt, das einen übersetzten regulären Ausdruck darstellt – was machst du damit? Musterobjekte haben mehrere Methoden und Attribute. Hier werden nur die wichtigsten behandelt. Eine vollständige Auflistung findest du in der Dokumentation zu re.
Methode/Attribut |
Zweck |
|---|---|
|
Eine Zeichenkette durchsuchen und jede Stelle finden, an der dieser RE passt. |
|
Feststellen, ob der RE am Anfang der Zeichenkette passt. Früher match() genannt. |
|
Alle Teilzeichenketten finden, auf die der RE passt, und sie als Liste zurückgeben. |
|
Alle Teilzeichenketten finden, auf die der RE passt, und sie als Iterator zurückgeben. |
search() und prefixmatch() geben None zurück, wenn keine Fundstelle gefunden wird. Sind sie erfolgreich, wird eine Instanz eines Match-Objekts zurückgegeben, die Informationen über die Fundstelle enthält: wo sie beginnt und endet, auf welche Teilzeichenkette sie gepasst hat und mehr.
Das lernst du am besten, indem du interaktiv mit dem Modul re herumprobierst.
Dieses HOWTO verwendet für seine Beispiele den gewöhnlichen Python-Interpreter. Starte zunächst den Python-Interpreter, importiere das Modul re und übersetze einen RE:
>>> import re
>>> p = re.compile('[a-z]+')
>>> p
re.compile('[a-z]+')
Jetzt kannst du verschiedene Zeichenketten gegen den RE [a-z]+ prüfen. Eine leere Zeichenkette sollte überhaupt nicht passen, denn + bedeutet „eine oder mehr Wiederholungen“. search() sollte in diesem Fall None zurückgeben, weshalb der Interpreter nichts ausgibt. Du kannst das Ergebnis von search() ausdrücklich ausgeben, um das deutlich zu machen.
>>> p.search("")
>>> print(p.search(""))
None
Probieren wir es nun an einer Zeichenkette, auf die er passen sollte, etwa tempo. In diesem Fall gibt search() ein Match-Objekt zurück, du solltest das Ergebnis also für die spätere Verwendung in einer Variablen ablegen.
>>> m = p.search('tempo')
>>> m
<re.Match object; span=(0, 5), match='tempo'>
Jetzt kannst du das Match-Objekt nach Informationen über die passende Zeichenkette befragen. Instanzen von Match-Objekten haben ebenfalls mehrere Methoden und Attribute. Die wichtigsten sind:
Methode/Attribut |
Zweck |
|---|---|
|
Gibt die Zeichenkette zurück, auf die der RE gepasst hat |
|
Gibt die Anfangsposition der Fundstelle zurück |
|
Gibt die Endposition der Fundstelle zurück |
|
Gibt ein Tupel mit den Positionen (Anfang, Ende) der Fundstelle zurück |
Diese Methoden auszuprobieren macht ihre Bedeutung schnell klar:
>>> m.group()
'tempo'
>>> m.start(), m.end()
(0, 5)
>>> m.span()
(0, 5)
group() gibt die Teilzeichenkette zurück, auf die der RE gepasst hat. start() und end() geben den Anfangs- und den Endindex der Fundstelle zurück. span() gibt Anfangs- und Endindex zusammen in einem einzigen Tupel zurück. Die Methode search() von Mustern durchsucht die Zeichenkette, die Fundstelle muss also nicht bei null beginnen. Die Methode prefixmatch() prüft dagegen nur, ob der RE am Anfang einer Zeichenkette passt, deshalb ist start() in diesem Fall immer null.
>>> m = p.search('::: message'); print(m)
<re.Match object; span=(4, 11), match='message'>
>>> m.group()
'message'
>>> m.span()
(4, 11)
>>> print(p.prefixmatch('::: message'))
None
In echten Programmen ist es am üblichsten, das Match-Objekt in einer Variablen abzulegen und dann zu prüfen, ob es None war. Das sieht meist so aus:
p = re.compile( ... )
m = p.search( 'string goes here' )
if m:
print('Match found: ', m.group())
else:
print('No match')
Zwei Methoden von Mustern geben alle Fundstellen eines Musters zurück. findall() gibt eine Liste der passenden Zeichenketten zurück:
>>> p = re.compile(r'\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']
Das Präfix r, das aus dem Literal ein Raw-String-Literal macht, ist in diesem Beispiel nötig, weil Escape-Sequenzen in einem gewöhnlichen („cooked“) Zeichenkettenliteral, die Python – im Unterschied zu regulären Ausdrücken – nicht kennt, jetzt zu einer SyntaxWarning führen und später zu einem SyntaxError werden. Siehe Die Backslash-Plage.
findall() muss die gesamte Liste aufbauen, bevor sie als Ergebnis zurückgegeben werden kann. Die Methode finditer() gibt eine Folge von Instanzen eines Match-Objekts als Iterator zurück:
>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
>>> iterator
<callable_iterator object at 0x...>
>>> for match in iterator:
... print(match.span())
...
(0, 2)
(22, 24)
(29, 31)
Funktionen auf Modulebene¶
Du musst kein Musterobjekt erzeugen und dessen Methoden aufrufen. Das Modul re bietet auch Funktionen auf oberster Ebene namens search(), prefixmatch(), findall(), sub() und so weiter. Diese Funktionen nehmen die gleichen Argumente wie die entsprechende Methode des Musters, ergänzt um die RE-Zeichenkette als erstes Argument, und geben weiterhin entweder None oder eine Instanz eines Match-Objekts zurück.
>>> print(re.prefixmatch(r'From\s+', 'Fromage amk'))
None
>>> re.prefixmatch(r'From\s+', 'From amk Thu May 14 19:12:10 1998')
<re.Match object; span=(0, 5), match='From '>
Unter der Haube erzeugen diese Funktionen einfach ein Musterobjekt für dich und rufen die passende Methode darauf auf. Sie legen das übersetzte Objekt außerdem in einem Zwischenspeicher ab, sodass spätere Aufrufe mit demselben RE das Muster nicht immer wieder neu zerlegen müssen.
Solltest du diese Funktionen auf Modulebene verwenden, oder solltest du dir das Muster holen und seine Methoden selbst aufrufen? Greifst du innerhalb einer Schleife auf einen Regex zu, sparst du durch vorheriges Übersetzen einige Funktionsaufrufe. Außerhalb von Schleifen macht es dank des internen Zwischenspeichers kaum einen Unterschied.
Flags beim Übersetzen¶
Flags beim Übersetzen erlauben dir, einige Aspekte der Arbeitsweise regulärer Ausdrücke zu verändern. Die Flags stehen im Modul re unter zwei Namen bereit: einem langen Namen wie IGNORECASE und einer kurzen Form aus einem Buchstaben wie I. (Kennst du Perls Mustermodifikatoren, so verwenden die Einbuchstabenformen die gleichen Buchstaben. Die Kurzform von re.VERBOSE ist zum Beispiel re.X.) Mehrere Flags lassen sich durch bitweises ODER angeben; re.I | re.M setzt zum Beispiel sowohl das Flag I als auch M.
Hier eine Tabelle der verfügbaren Flags, gefolgt von einer ausführlicheren Erklärung jedes einzelnen.
Flag |
Bedeutung |
|---|---|
Lässt mehrere Escapes wie |
|
Lässt |
|
Sucht ohne Rücksicht auf Groß- und Kleinschreibung. |
|
Sucht unter Berücksichtigung des Gebietsschemas. |
|
Mehrzeilige Suche, wirkt auf |
|
Schaltet ausführliche REs ein, die sich übersichtlicher und verständlicher gliedern lassen. |
- re.I
- re.IGNORECASE
Sucht ohne Rücksicht auf Groß- und Kleinschreibung; Zeichenklassen und wörtliche Zeichenketten passen auf Buchstaben, ohne die Schreibweise zu beachten.
[A-Z]passt dann zum Beispiel auch auf Kleinbuchstaben. Vollständiges Unicode-Verhalten gilt ebenfalls, sofern nicht das FlagASCIIFundstellen außerhalb von ASCII abschaltet. Werden die Unicode-Muster[a-z]oder[A-Z]mit dem FlagIGNORECASEverwendet, passen sie auf die 52 ASCII-Buchstaben und 4 weitere Buchstaben außerhalb von ASCII: „İ“ (U+0130, lateinischer Großbuchstabe I mit Punkt darüber), „ı“ (U+0131, lateinischer Kleinbuchstabe i ohne Punkt), „ſ“ (U+017F, lateinischer Kleinbuchstabe langes s) und „K“ (U+212A, Kelvin-Zeichen).Spampasst auf'Spam','spam','spAM'oder'ſpam'(letzteres nur im Unicode-Modus). Dieses Kleinschreiben berücksichtigt das aktuelle Gebietsschema nicht. Das tut es, wenn du zusätzlich das FlagLOCALEsetzt.
- re.L
- re.LOCALE
Macht
\w,\W,\b,\Bund die Suche ohne Rücksicht auf Groß- und Kleinschreibung vom aktuellen Gebietsschema abhängig statt von der Unicode-Datenbank.Gebietsschemata sind eine Einrichtung der C-Bibliothek, die beim Schreiben von Programmen helfen soll, die sprachliche Unterschiede berücksichtigen. Verarbeitest du zum Beispiel kodierten französischen Text, möchtest du
\w+schreiben können, um auf Wörter zu passen, aber\wpasst in Bytes-Mustern nur auf die Zeichenklasse[A-Za-z]. Es passt nicht auf die Bytes, dieéoderçentsprechen. Ist dein System richtig eingerichtet und ein französisches Gebietsschema ausgewählt, teilen bestimmte C-Funktionen dem Programm mit, dass das Byte füréebenfalls als Buchstabe gelten soll. Setzt du beim Übersetzen eines regulären Ausdrucks das FlagLOCALE, verwendet das entstehende übersetzte Objekt für\wdiese C-Funktionen. Das ist langsamer, dafür passt\w+dann wie erwartet auf französische Wörter. Von diesem Flag wird in Python 3 abgeraten, weil der Gebietsschema-Mechanismus sehr unzuverlässig ist, immer nur einen „Kulturraum“ gleichzeitig behandelt und nur mit 8-Bit-Gebietsschemata funktioniert. Unicode-Verhalten ist in Python 3 für Unicode-Muster (str) ohnehin standardmäßig eingeschaltet und kommt mit verschiedenen Gebietsschemata und Sprachen zurecht.
- re.M
- re.MULTILINE
(
^und$wurden noch nicht erklärt. Sie werden im Abschnitt Weitere Metazeichen eingeführt.)Normalerweise passt
^nur am Anfang der Zeichenkette, und$passt nur am Ende der Zeichenkette sowie unmittelbar vor dem Zeilenumbruch (falls vorhanden) am Ende der Zeichenkette. Ist dieses Flag angegeben, passt^am Anfang der Zeichenkette und am Anfang jeder Zeile innerhalb der Zeichenkette, also unmittelbar nach jedem Zeilenumbruch. Entsprechend passt das Metazeichen$sowohl am Ende der Zeichenkette als auch am Ende jeder Zeile (also unmittelbar vor jedem Zeilenumbruch).
- re.S
- re.DOTALL
Lässt das besondere Zeichen
'.'auf wirklich jedes Zeichen passen, auch auf einen Zeilenumbruch. Ohne dieses Flag passt'.'auf alles außer einem Zeilenumbruch.
- re.A
- re.ASCII
Lässt
\w,\W,\b,\B,\sund\Snur auf ASCII passen statt auf vollständiges Unicode. Das ist nur für Unicode-Muster von Bedeutung und wird bei Bytes-Mustern ignoriert.
- re.X
- re.VERBOSE
Mit diesem Flag kannst du besser lesbare reguläre Ausdrücke schreiben, weil du mehr Freiheit bei ihrer Formatierung hast. Ist dieses Flag angegeben, wird Leerraum innerhalb der RE-Zeichenkette ignoriert, außer wenn er in einer Zeichenklasse steht oder ein nicht maskierter Backslash davorsteht. Damit kannst du den RE übersichtlicher gliedern und einrücken. Dieses Flag erlaubt dir außerdem, Kommentare in einen RE zu schreiben, die die Maschine ignoriert; Kommentare werden mit einem
'#'eingeleitet, das weder in einer Zeichenklasse steht noch einen nicht maskierten Backslash davor hat.Hier zum Beispiel ein RE, der
re.VERBOSEverwendet. Siehst du, wie viel leichter er zu lesen ist?charref = re.compile(r""" &[#] # Start of a numeric entity reference ( 0[0-7]+ # Octal form | [0-9]+ # Decimal form | x[0-9a-fA-F]+ # Hexadecimal form ) ; # Trailing semicolon """, re.VERBOSE)
Ohne die ausführliche Einstellung sähe der RE so aus:
charref = re.compile("&#(0[0-7]+" "|[0-9]+" "|x[0-9a-fA-F]+);")
Im obigen Beispiel wurde Pythons automatisches Verketten von Zeichenkettenliteralen genutzt, um den RE in kleinere Stücke zu zerlegen, aber er ist immer noch schwerer zu verstehen als die Fassung mit
re.VERBOSE.
Mehr Kraft im Muster¶
Bisher haben wir nur einen Teil der Fähigkeiten regulärer Ausdrücke behandelt. In diesem Abschnitt behandeln wir einige neue Metazeichen und wie du mit Gruppen Teile des gefundenen Textes auslesen kannst.
Weitere Metazeichen¶
Es gibt einige Metazeichen, die wir noch nicht behandelt haben. Die meisten davon kommen in diesem Abschnitt zur Sprache.
Einige der noch zu besprechenden Metazeichen sind Zusicherungen der Breite null. Sie bringen die Maschine nicht dazu, in der Zeichenkette voranzugehen. Sie verbrauchen überhaupt keine Zeichen und gelingen oder scheitern einfach. \b ist zum Beispiel eine Zusicherung, dass die aktuelle Position an einer Wortgrenze liegt. Die Position wird durch das \b überhaupt nicht verändert. Das bedeutet, dass Zusicherungen der Breite null niemals wiederholt werden sollten, denn wenn sie an einer Stelle einmal passen, können sie dort offensichtlich unendlich oft passen.
|Alternative, also der „oder“-Operator. Sind A und B reguläre Ausdrücke, passt
A|Bauf jede Zeichenkette, auf die entweder A oder B passt.|hat eine sehr niedrige Bindungsstärke, damit es vernünftig arbeitet, wenn du Zeichenketten aus mehreren Zeichen gegeneinanderstellst.Crow|Servopasst entweder auf'Crow'oder auf'Servo', nicht auf'Cro', ein'w'oder ein'S'und'ervo'.Um auf ein wörtliches
'|'zu passen, verwende\|oder schließe es in eine Zeichenklasse ein, etwa als[|].^Passt am Anfang von Zeilen. Sofern nicht das Flag
MULTILINEgesetzt ist, passt es nur am Anfang der Zeichenkette. Im ModusMULTILINEpasst es außerdem unmittelbar nach jedem Zeilenumbruch innerhalb der Zeichenkette.Willst du zum Beispiel auf das Wort
Fromnur am Zeilenanfang passen, lautet der zu verwendende RE^From.>>> print(re.search('^From', 'From Here to Eternity')) <re.Match object; span=(0, 4), match='From'> >>> print(re.search('^From', 'Reciting From Memory')) None
Um auf ein wörtliches
'^'zu passen, verwende\^.$Passt am Ende einer Zeile, das entweder als Ende der Zeichenkette oder als jede Stelle definiert ist, auf die ein Zeilenumbruchzeichen folgt.
>>> print(re.search('}$', '{block}')) <re.Match object; span=(6, 7), match='}'> >>> print(re.search('}$', '{block} ')) None >>> print(re.search('}$', '{block}\n')) <re.Match object; span=(6, 7), match='}'>
Um auf ein wörtliches
'$'zu passen, verwende\$oder schließe es in eine Zeichenklasse ein, etwa als[$].\APasst nur am Anfang der Zeichenkette. Außerhalb des Modus
MULTILINEsind\Aund^praktisch dasselbe. Im ModusMULTILINEunterscheiden sie sich:\Apasst weiterhin nur am Anfang der Zeichenkette,^kann aber an jeder Stelle innerhalb der Zeichenkette passen, die auf ein Zeilenumbruchzeichen folgt.\zPasst nur am Ende der Zeichenkette.
\ZDasselbe wie
\z. Zur Verträglichkeit mit alten Python-Versionen.\bWortgrenze. Das ist eine Zusicherung der Breite null, die nur am Anfang oder Ende eines Wortes passt. Ein Wort ist als Folge alphanumerischer Zeichen definiert, das Ende eines Wortes wird also durch Leerraum oder ein nicht alphanumerisches Zeichen angezeigt.
Das folgende Beispiel passt auf
classnur dann, wenn es ein vollständiges Wort ist. Es passt nicht, wenn es in einem anderen Wort steckt.>>> p = re.compile(r'\bclass\b') >>> print(p.search('no class at all')) <re.Match object; span=(3, 8), match='class'> >>> print(p.search('the declassified algorithm')) None >>> print(p.search('one subclass is')) None
Bei dieser besonderen Sequenz solltest du an zwei Feinheiten denken. Erstens ist das die schlimmste Kollision zwischen Pythons Zeichenkettenliteralen und den Sequenzen regulärer Ausdrücke. In Pythons Zeichenkettenliteralen ist
\bdas Rückschrittzeichen mit dem ASCII-Wert 8. Verwendest du keine Raw-Strings, wandelt Python das\bin einen Rückschritt um, und dein RE passt nicht wie erwartet. Das folgende Beispiel sieht genauso aus wie unser vorheriger RE, lässt aber das'r'vor der RE-Zeichenkette weg.>>> p = re.compile('\bclass\b') >>> print(p.search('no class at all')) None >>> print(p.search('\b' + 'class' + '\b')) <re.Match object; span=(0, 7), match='\x08class\x08'>
Zweitens steht
\binnerhalb einer Zeichenklasse, wo diese Zusicherung keinen Sinn hätte, für das Rückschrittzeichen – zur Verträglichkeit mit Pythons Zeichenkettenliteralen.\BEine weitere Zusicherung der Breite null. Sie ist das Gegenteil von
\bund passt nur, wenn die aktuelle Position nicht an einer Wortgrenze liegt.
Gruppierung¶
Häufig brauchst du mehr Informationen als nur die, ob der RE gepasst hat oder nicht. Reguläre Ausdrücke werden oft verwendet, um Zeichenketten zu zerlegen. Dazu schreibt man einen RE, der in mehrere Untergruppen aufgeteilt ist, die auf die verschiedenen interessierenden Bestandteile passen. Eine Kopfzeile nach RFC 822 ist zum Beispiel in einen Kopfzeilennamen und einen Wert aufgeteilt, getrennt durch ein ':', etwa so:
From: author@example.com
User-Agent: Thunderbird 1.5.0.9 (X11/20061227)
MIME-Version: 1.0
To: editor@example.com
Das lässt sich handhaben, indem man einen regulären Ausdruck schreibt, der auf eine ganze Kopfzeile passt und eine Gruppe für den Kopfzeilennamen sowie eine weitere Gruppe für den Wert der Kopfzeile hat.
Gruppen werden mit den Metazeichen '(' und ')' markiert. '(' und ')' haben weitgehend dieselbe Bedeutung wie in mathematischen Ausdrücken. Sie fassen die darin enthaltenen Ausdrücke zusammen, und du kannst den Inhalt einer Gruppe mit einem Quantor wie *, +, ? oder {m,n} wiederholen. (ab)* passt zum Beispiel auf null oder mehr Wiederholungen von ab.
>>> p = re.compile('(ab)*')
>>> print(p.search('ababababab').span())
(0, 10)
Gruppen, die mit '(' und ')' angezeigt werden, erfassen außerdem den Anfangs- und Endindex des Textes, auf den sie passen. Die lassen sich auslesen, indem du group(), start(), end() und span() ein Argument übergibst. Gruppen werden ab 0 gezählt. Gruppe 0 ist immer vorhanden. Sie ist der gesamte RE, deshalb haben alle Methoden des Match-Objekts Gruppe 0 als Standardargument. Später sehen wir, wie man Gruppen formuliert, die den Textbereich, auf den sie passen, nicht erfassen.
>>> p = re.compile('(a)b')
>>> m = p.search('ab')
>>> m.group()
'ab'
>>> m.group(0)
'ab'
Untergruppen werden von links nach rechts ab 1 aufwärts gezählt. Gruppen können verschachtelt sein. Um die Nummer zu bestimmen, zähle einfach die öffnenden Klammern von links nach rechts.
>>> p = re.compile('(a(b)c)d')
>>> m = p.search('abcd')
>>> m.group(0)
'abcd'
>>> m.group(1)
'abc'
>>> m.group(2)
'b'
group() lassen sich mehrere Gruppennummern auf einmal übergeben. In diesem Fall gibt sie ein Tupel mit den entsprechenden Werten dieser Gruppen zurück.
>>> m.group(2,1,2)
('b', 'abc', 'b')
Die Methode groups() gibt ein Tupel mit den Zeichenketten aller Untergruppen zurück, von 1 bis zu so vielen, wie es gibt.
>>> m.groups()
('abc', 'b')
Rückwärtsverweise in einem Muster erlauben dir anzugeben, dass der Inhalt einer früheren erfassenden Gruppe auch an der aktuellen Stelle in der Zeichenkette zu finden sein muss. \1 gelingt zum Beispiel, wenn genau der Inhalt von Gruppe 1 an der aktuellen Position gefunden wird, und scheitert andernfalls. Denk daran, dass Pythons Zeichenkettenliterale ebenfalls einen Backslash gefolgt von Zahlen verwenden, um beliebige Zeichen in eine Zeichenkette aufzunehmen. Verwende also unbedingt einen Raw-String, wenn du Rückwärtsverweise in einen RE einbaust.
Der folgende RE findet zum Beispiel doppelte Wörter in einer Zeichenkette.
>>> p = re.compile(r'\b(\w+)\s+\1\b')
>>> p.search('Paris in the the spring').group()
'the the'
Solche Rückwärtsverweise sind zum reinen Durchsuchen einer Zeichenkette selten nützlich — es gibt wenige Textformate, die Daten auf diese Weise wiederholen —, aber du wirst schnell merken, dass sie beim Ersetzen in Zeichenketten sehr nützlich sind.
Nicht erfassende und benannte Gruppen¶
Ausgefeilte REs verwenden möglicherweise viele Gruppen, sowohl um interessierende Teilzeichenketten zu erfassen als auch um den RE selbst zu gliedern und zu strukturieren. In komplexen REs wird es schwierig, die Gruppennummern im Blick zu behalten. Es gibt zwei Einrichtungen, die bei diesem Problem helfen. Beide nutzen eine gemeinsame Syntax für Erweiterungen regulärer Ausdrücke, die wir uns daher zuerst ansehen.
Perl 5 ist für seine mächtigen Ergänzungen zu den gewöhnlichen regulären Ausdrücken bekannt. Für diese neuen Fähigkeiten konnten die Perl-Entwickler keine neuen Metazeichen aus einem Tastendruck und keine neuen besonderen Sequenzen mit \ wählen, ohne Perls reguläre Ausdrücke verwirrend anders als die gewöhnlichen REs zu machen. Hätten sie zum Beispiel & als neues Metazeichen gewählt, hätten alte Ausdrücke angenommen, dass & ein gewöhnliches Zeichen ist, und es nicht als \& oder [&] maskiert.
Die von den Perl-Entwicklern gewählte Lösung war, (?...) als Erweiterungssyntax zu verwenden. Ein ? unmittelbar nach einer Klammer war ein Syntaxfehler, weil das ? nichts zu wiederholen gehabt hätte. So entstanden keine Verträglichkeitsprobleme. Die Zeichen unmittelbar nach dem ? zeigen an, welche Erweiterung verwendet wird, (?=foo) ist also eine Sache (eine positive Lookahead-Zusicherung) und (?:foo) etwas anderes (eine nicht erfassende Gruppe mit dem Teilausdruck foo).
Python unterstützt mehrere von Perls Erweiterungen und ergänzt Perls Erweiterungssyntax um eine eigene Erweiterungssyntax. Ist das erste Zeichen nach dem Fragezeichen ein P, weißt du, dass es sich um eine Erweiterung handelt, die es nur in Python gibt.
Nachdem wir uns die allgemeine Erweiterungssyntax angesehen haben, können wir zu den Einrichtungen zurückkehren, die die Arbeit mit Gruppen in komplexen REs vereinfachen.
Manchmal willst du mit einer Gruppe einen Teil eines regulären Ausdrucks abgrenzen, bist aber nicht daran interessiert, den Inhalt der Gruppe auszulesen. Das kannst du ausdrücklich machen, indem du eine nicht erfassende Gruppe verwendest: (?:...), wobei du die ... durch jeden anderen regulären Ausdruck ersetzen kannst.
>>> m = re.search("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.search("(?:[abc])+", "abc")
>>> m.groups()
()
Abgesehen davon, dass du den Inhalt nicht auslesen kannst, auf den die Gruppe gepasst hat, verhält sich eine nicht erfassende Gruppe genau wie eine erfassende. Du kannst alles hineinschreiben, sie mit einem Wiederholungsmetazeichen wie * wiederholen und sie in andere Gruppen (erfassende oder nicht erfassende) verschachteln. (?:...) ist besonders nützlich, wenn du ein bestehendes Muster änderst, denn du kannst neue Gruppen ergänzen, ohne die Nummerierung aller anderen Gruppen zu verändern. Es sei erwähnt, dass es beim Suchen keinen Geschwindigkeitsunterschied zwischen erfassenden und nicht erfassenden Gruppen gibt. Keine der beiden Formen ist schneller als die andere.
Eine wichtigere Einrichtung sind benannte Gruppen: Statt sie über Nummern anzusprechen, lassen sich Gruppen über einen Namen ansprechen.
Die Syntax für eine benannte Gruppe ist eine der Erweiterungen, die es nur in Python gibt: (?P<name>...). name ist, wie zu erwarten, der Name der Gruppe. Benannte Gruppen verhalten sich genau wie erfassende Gruppen und verbinden zusätzlich einen Namen mit einer Gruppe. Die Methoden des Match-Objekts, die mit erfassenden Gruppen umgehen, nehmen alle entweder Ganzzahlen, die die Gruppe über ihre Nummer ansprechen, oder Zeichenketten mit dem Namen der gewünschten Gruppe. Benannte Gruppen erhalten weiterhin Nummern, du kannst Informationen über eine Gruppe also auf zwei Wegen auslesen:
>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'
Darüber hinaus kannst du benannte Gruppen mit groupdict() als Dictionary auslesen:
>>> m = re.search(r'(?P<first>\w+) (?P<last>\w+)', 'Jane Doe')
>>> m.groupdict()
{'first': 'Jane', 'last': 'Doe'}
Benannte Gruppen sind praktisch, weil du damit leicht einprägsame Namen verwenden kannst, statt dir Nummern merken zu müssen. Hier ein Beispiel-RE aus dem Modul imaplib:
InternalDate = re.compile(r'INTERNALDATE "'
r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
r'(?P<year>[0-9][0-9][0-9][0-9])'
r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
r'"')
Es ist offensichtlich viel einfacher, m.group('zonem') auszulesen, als sich zu merken, dass man Gruppe 9 auslesen muss.
Die Syntax für Rückwärtsverweise in einem Ausdruck wie (...)\1 spricht die Nummer der Gruppe an. Es gibt natürlich eine Variante, die den Gruppennamen statt der Nummer verwendet. Das ist eine weitere Python-Erweiterung: (?P=name) zeigt an, dass der Inhalt der Gruppe namens name an der aktuellen Stelle erneut passen soll. Der reguläre Ausdruck zum Finden doppelter Wörter, \b(\w+)\s+\1\b, lässt sich auch als \b(?P<word>\w+)\s+(?P=word)\b schreiben:
>>> p = re.compile(r'\b(?P<word>\w+)\s+(?P=word)\b')
>>> p.search('Paris in the the spring').group()
'the the'
Lookahead-Zusicherungen¶
Eine weitere Zusicherung der Breite null ist die Lookahead-Zusicherung. Lookahead-Zusicherungen gibt es in positiver und negativer Form, und sie sehen so aus:
(?=...)Positive Lookahead-Zusicherung. Sie gelingt, wenn der enthaltene reguläre Ausdruck, hier durch
...dargestellt, an der aktuellen Stelle erfolgreich passt, und scheitert andernfalls. Nachdem der enthaltene Ausdruck aber versucht wurde, geht die Suchmaschine überhaupt nicht voran. Der Rest des Musters wird genau dort versucht, wo die Zusicherung begonnen hat.(?!...)Negative Lookahead-Zusicherung. Sie ist das Gegenteil der positiven Zusicherung. Sie gelingt, wenn der enthaltene Ausdruck an der aktuellen Position in der Zeichenkette nicht passt.
Um das greifbar zu machen, sehen wir uns einen Fall an, in dem ein Lookahead nützlich ist. Stell dir ein einfaches Muster vor, das auf einen Dateinamen passt und ihn in einen Basisnamen und eine Endung zerlegt, getrennt durch einen .. In news.rc ist zum Beispiel news der Basisname und rc die Endung des Dateinamens.
Das Muster dafür ist recht einfach:
.*[.].*$
Beachte, dass der . besonders behandelt werden muss, weil er ein Metazeichen ist. Er steht deshalb in einer Zeichenklasse, damit er nur auf genau dieses Zeichen passt. Beachte außerdem das abschließende $. Es sorgt dafür, dass der gesamte Rest der Zeichenkette zur Endung gehören muss. Dieser reguläre Ausdruck passt auf foo.bar, autoexec.bat, sendmail.cf und printers.conf.
Machen wir das Problem nun etwas komplizierter: Was, wenn du auf Dateinamen passen willst, deren Endung nicht bat ist? Einige fehlerhafte Versuche:
.*[.][^b].*$
Der erste Versuch oben will bat ausschließen, indem er verlangt, dass das erste Zeichen der Endung kein b ist. Das ist falsch, weil das Muster dann auch nicht auf foo.bar passt.
.*[.]([^b]..|.[^a].|..[^t])$
Der Ausdruck wird unübersichtlicher, wenn du die erste Lösung ausbessern willst, indem du verlangst, dass einer der folgenden Fälle passt: Das erste Zeichen der Endung ist kein b, das zweite Zeichen ist kein a, oder das dritte Zeichen ist kein t. Das nimmt foo.bar an und weist autoexec.bat zurück, verlangt aber eine dreibuchstabige Endung und nimmt keinen Dateinamen mit zweibuchstabiger Endung wie sendmail.cf an. Wir machen das Muster also erneut komplizierter, um das zu beheben.
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
Im dritten Versuch werden der zweite und dritte Buchstabe optional gemacht, damit auch Endungen mit weniger als drei Zeichen passen, etwa sendmail.cf.
Das Muster wird jetzt richtig kompliziert, was es schwer lesbar und schwer verständlich macht. Schlimmer noch: Ändert sich die Aufgabe und du willst sowohl bat als auch exe als Endung ausschließen, würde das Muster noch komplizierter und verwirrender.
Ein negativer Lookahead schneidet diese ganze Verwirrung ab:
.*[.](?!bat$)[^.]*$
Der negative Lookahead bedeutet: Passt der Ausdruck bat an dieser Stelle nicht, versuche den Rest des Musters. Passt bat$ doch, scheitert das gesamte Muster. Das abschließende $ ist nötig, damit etwas wie sample.batch, dessen Endung nur mit bat beginnt, zugelassen wird. Das [^.]* stellt sicher, dass das Muster auch dann funktioniert, wenn der Dateiname mehrere Punkte enthält.
Eine weitere Dateiendung auszuschließen ist nun leicht. Ergänze sie einfach als Alternative innerhalb der Zusicherung. Das folgende Muster schließt Dateinamen aus, die entweder auf bat oder auf exe enden:
.*[.](?!bat$|exe$)[^.]*$
Zeichenketten verändern¶
Bis hierher haben wir nur in einer unveränderlichen Zeichenkette gesucht. Reguläre Ausdrücke werden auch häufig verwendet, um Zeichenketten auf verschiedene Weisen zu verändern. Dazu dienen die folgenden Methoden von Mustern:
Methode/Attribut |
Zweck |
|---|---|
|
Zerlegt die Zeichenkette in eine Liste und trennt sie überall, wo der RE passt |
|
Findet alle Teilzeichenketten, auf die der RE passt, und ersetzt sie durch eine andere Zeichenkette |
|
Tut dasselbe wie |
Zeichenketten zerlegen¶
Die Methode split() eines Musters zerlegt eine Zeichenkette überall, wo der RE passt, und gibt eine Liste der Stücke zurück. Sie ähnelt der Methode split() von Zeichenketten, ist bei den Trennzeichen, nach denen du zerlegen kannst, aber viel allgemeiner; split() von Zeichenketten kann nur nach Leerraum oder nach einer festen Zeichenkette zerlegen. Wie zu erwarten gibt es auch eine Funktion re.split() auf Modulebene.
- .split(string[, maxsplit=0])
Zerlegt string an den Fundstellen des regulären Ausdrucks. Werden im RE erfassende Klammern verwendet, wird auch ihr Inhalt als Teil der entstehenden Liste zurückgegeben. Ist maxsplit ungleich null, werden höchstens maxsplit Trennungen vorgenommen.
Du kannst die Anzahl der Trennungen begrenzen, indem du einen Wert für maxsplit übergibst. Ist maxsplit ungleich null, werden höchstens maxsplit Trennungen vorgenommen, und der Rest der Zeichenkette wird als letztes Element der Liste zurückgegeben. Im folgenden Beispiel ist das Trennzeichen jede Folge nicht alphanumerischer Zeichen.
>>> p = re.compile(r'\W+')
>>> p.split('This is a test, short and sweet, of split().')
['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
>>> p.split('This is a test, short and sweet, of split().', 3)
['This', 'is', 'a', 'test, short and sweet, of split().']
Manchmal interessiert dich nicht nur, was der Text zwischen den Trennzeichen ist, sondern du musst auch wissen, welches das Trennzeichen war. Werden im RE erfassende Klammern verwendet, werden auch ihre Werte als Teil der Liste zurückgegeben. Vergleiche die folgenden Aufrufe:
>>> p = re.compile(r'\W+')
>>> p2 = re.compile(r'(\W+)')
>>> p.split('This... is a test.')
['This', 'is', 'a', 'test', '']
>>> p2.split('This... is a test.')
['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']
Die Funktion re.split() auf Modulebene ergänzt den zu verwendenden RE als erstes Argument, ist ansonsten aber gleich.
>>> re.split(r'[\W]+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'([\W]+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'[\W]+', 'Words, words, words.', 1)
['Words', 'words, words.']
Suchen und ersetzen¶
Eine weitere häufige Aufgabe ist, alle Fundstellen eines Musters zu finden und sie durch eine andere Zeichenkette zu ersetzen. Die Methode sub() nimmt einen Ersatzwert, der entweder eine Zeichenkette oder eine Funktion sein kann, sowie die zu verarbeitende Zeichenkette.
- .sub(replacement, string[, count=0])
Gibt die Zeichenkette zurück, die entsteht, wenn die von links her ersten, sich nicht überlappenden Vorkommen des RE in string durch den Ersatz replacement ersetzt werden. Wird das Muster nicht gefunden, wird string unverändert zurückgegeben.
Das optionale Argument count ist die größte Anzahl zu ersetzender Vorkommen des Musters; count muss eine nicht negative Ganzzahl sein. Der Standardwert 0 bedeutet, dass alle Vorkommen ersetzt werden.
Hier ein einfaches Beispiel für die Methode sub(). Sie ersetzt Farbnamen durch das Wort colour:
>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'
>>> p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'
Die Methode subn() leistet dieselbe Arbeit, gibt aber ein 2-Tupel mit der neuen Zeichenkette und der Anzahl der vorgenommenen Ersetzungen zurück:
>>> p = re.compile('(blue|white|red)')
>>> p.subn('colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)
>>> p.subn('colour', 'no colours at all')
('no colours at all', 0)
Leere Fundstellen werden nur dann ersetzt, wenn sie nicht unmittelbar an eine vorherige leere Fundstelle angrenzen.
>>> p = re.compile('x*')
>>> p.sub('-', 'abxd')
'-a-b--d-'
Ist replacement eine Zeichenkette, werden alle Backslash-Maskierungen darin verarbeitet. Das heißt, \n wird in ein einzelnes Zeilenumbruchzeichen umgewandelt, \r in einen Wagenrücklauf und so weiter. Unbekannte Maskierungen wie \& bleiben unangetastet. Rückwärtsverweise wie \6 werden durch die Teilzeichenkette ersetzt, auf die die entsprechende Gruppe im RE gepasst hat. Damit kannst du Teile des ursprünglichen Textes in die entstehende Ersatzzeichenkette einbauen.
Dieses Beispiel passt auf das Wort section, gefolgt von einer in { und } eingeschlossenen Zeichenkette, und ändert section in subsection:
>>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First} section{second}')
'subsection{First} subsection{second}'
Es gibt außerdem eine Syntax, um benannte Gruppen anzusprechen, wie sie die Syntax (?P<name>...) festlegt. \g<name> verwendet die Teilzeichenkette, auf die die Gruppe namens name gepasst hat, und \g<number> verwendet die entsprechende Gruppennummer. \g<2> entspricht daher \2, ist in einer Ersatzzeichenkette wie \g<2>0 aber nicht mehrdeutig. (\20 würde als Verweis auf Gruppe 20 verstanden, nicht als Verweis auf Gruppe 2 gefolgt vom wörtlichen Zeichen '0'.) Die folgenden Ersetzungen sind alle gleichwertig, verwenden aber alle drei Varianten der Ersatzzeichenkette.
>>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<1>}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<name>}','section{First}')
'subsection{First}'
replacement kann auch eine Funktion sein, was dir noch mehr Kontrolle gibt. Ist replacement eine Funktion, wird sie für jedes sich nicht überlappende Vorkommen von pattern aufgerufen. Bei jedem Aufruf wird der Funktion ein Match-Objekt für die Fundstelle übergeben. Sie kann daraus die gewünschte Ersatzzeichenkette berechnen und zurückgeben.
Im folgenden Beispiel übersetzt die Ersatzfunktion Dezimalzahlen in Hexadezimalzahlen:
>>> def hexrepl(match):
... "Return the hex string for a decimal number"
... value = int(match.group())
... return hex(value)
...
>>> p = re.compile(r'\d+')
>>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
'Call 0xffd2 for printing, 0xc000 for user code.'
Verwendest du die Funktion re.sub() auf Modulebene, wird das Muster als erstes Argument übergeben. Das Muster darf als Objekt oder als Zeichenkette angegeben werden. Musst du Flags für reguläre Ausdrücke angeben, musst du entweder ein Musterobjekt als ersten Parameter verwenden oder eingebettete Modifikatoren in der Musterzeichenkette, etwa gibt sub("(?i)b+", "x", "bbbb BBBB") den Wert 'x x' zurück.
Häufige Probleme¶
Reguläre Ausdrücke sind für manche Anwendungen ein mächtiges Werkzeug, aber ihr Verhalten ist in einigen Punkten nicht anschaulich, und manchmal verhalten sie sich nicht so, wie du es erwarten würdest. Dieser Abschnitt zeigt einige der häufigsten Fallgruben auf.
Methoden von Zeichenketten verwenden¶
Manchmal ist es ein Fehler, das Modul re zu verwenden. Passt du auf eine feste Zeichenkette oder eine einzelne Zeichenklasse und nutzt keine Eigenschaften von re wie das Flag IGNORECASE, dann brauchst du vielleicht nicht die ganze Kraft regulärer Ausdrücke. Zeichenketten haben mehrere Methoden für Operationen mit festen Zeichenketten, und die sind meist viel schneller, weil die Umsetzung eine einzige kleine, dafür optimierte C-Schleife ist statt der großen, allgemeiner gehaltenen Maschine für reguläre Ausdrücke.
Ein Beispiel wäre, eine einzelne feste Zeichenkette durch eine andere zu ersetzen. Etwa word durch deed. re.sub() erscheint als die passende Funktion dafür, aber sieh dir die Methode replace() an. Beachte, dass replace() auch word innerhalb von Wörtern ersetzt und aus swordfish ein sdeedfish macht – aber der naive RE word hätte das genauso getan. (Um die Ersetzung nicht in Wortteilen vorzunehmen, müsste das Muster \bword\b lauten, damit word auf beiden Seiten eine Wortgrenze haben muss. Das geht über die Fähigkeiten von replace() hinaus.)
Eine weitere häufige Aufgabe ist, jedes Vorkommen eines einzelnen Zeichens aus einer Zeichenkette zu entfernen oder durch ein anderes einzelnes Zeichen zu ersetzen. Das könntest du mit etwas wie re.sub('\n', ' ', S) machen, aber translate() kann beide Aufgaben erledigen und ist schneller, als eine Operation mit regulären Ausdrücken es sein kann.
Kurz gesagt: Überlege vor dem Griff zum Modul re, ob sich dein Problem mit einer schnelleren und einfacheren Methode von Zeichenketten lösen lässt.
prefixmatch() (auch match) gegenüber search()¶
prefixmatch() kam in Python 3.15 als bevorzugter Name für match() hinzu. Davor hieß es nur match(), und der Unterschied zu search() wurde oft missverstanden.
prefixmatch(), also match(), prüft nur, ob der RE am Anfang der Zeichenkette passt, während search() die Zeichenkette vorwärts nach einer Fundstelle durchsucht.
>>> print(re.prefixmatch('super', 'superstition').span())
(0, 5)
>>> print(re.prefixmatch('super', 'insuperable'))
None
search() durchsucht die Zeichenkette dagegen vorwärts und meldet die erste Fundstelle, die es findet.
>>> print(re.search('super', 'superstition').span())
(0, 5)
>>> print(re.search('super', 'insuperable').span())
(2, 7)
An diesen Unterschied solltest du denken, wenn du den alten Namen match() in Code verwendest, der mit älteren Python-Versionen verträglich sein muss.
Gierig gegenüber nicht gierig¶
Beim Wiederholen eines regulären Ausdrucks, etwa in a*, wird so viel vom Muster verbraucht wie möglich. Diese Eigenschaft beißt dich oft, wenn du auf ein Paar ausgeglichener Trennzeichen passen willst, etwa auf die spitzen Klammern um ein HTML-Tag. Das naive Muster zum Passen auf ein einzelnes HTML-Tag funktioniert wegen der gierigen Natur von .* nicht.
>>> s = '<html><head><title>Title</title>'
>>> len(s)
32
>>> print(re.prefixmatch('<.*>', s).span())
(0, 32)
>>> print(re.prefixmatch('<.*>', s).group())
<html><head><title>Title</title>
Der RE passt auf das '<' in '<html>', und das .* verbraucht den Rest der Zeichenkette. Im RE steht aber noch mehr, und das > kann am Ende der Zeichenkette nicht passen, also muss die Maschine für reguläre Ausdrücke Zeichen für Zeichen zurückgehen, bis sie eine Fundstelle für das > findet. Die endgültige Fundstelle reicht vom '<' in '<html>' bis zum '>' in '</title>', und das ist nicht, was du willst.
Die Lösung besteht in diesem Fall darin, die nicht gierigen Quantoren *?, +?, ?? oder {m,n}? zu verwenden, die auf so wenig Text wie möglich passen. Im obigen Beispiel wird das '>' unmittelbar nach der ersten Fundstelle des '<' versucht, und wenn es fehlschlägt, geht die Maschine Zeichen für Zeichen voran und versucht das '>' bei jedem Schritt neu. Das liefert genau das richtige Ergebnis:
>>> print(re.prefixmatch('<.*?>', s).group())
<html>
(Beachte, dass das Zerlegen von HTML oder XML mit regulären Ausdrücken mühsam ist. Schnell hingeworfene Muster bewältigen die häufigen Fälle, aber HTML und XML haben Sonderfälle, an denen der naheliegende reguläre Ausdruck zerbricht, bis du einen regulären Ausdruck geschrieben hast, der alle möglichen Fälle behandelt, sind die Muster sehr kompliziert. Verwende für solche Aufgaben ein Modul zum Zerlegen von HTML oder XML.)
re.VERBOSE verwenden¶
Inzwischen ist dir wahrscheinlich aufgefallen, dass reguläre Ausdrücke eine sehr knappe Schreibweise sind, aber nicht besonders lesbar. REs mittlerer Komplexität können zu langen Ansammlungen von Backslashes, Klammern und Metazeichen werden und sind dann schwer zu lesen und zu verstehen.
Bei solchen REs kann es helfen, beim Übersetzen des regulären Ausdrucks das Flag re.VERBOSE anzugeben, denn damit kannst du den regulären Ausdruck übersichtlicher formatieren.
Das Flag re.VERBOSE hat mehrere Wirkungen. Leerraum im regulären Ausdruck, der nicht in einer Zeichenklasse steht, wird ignoriert. Das bedeutet, dass ein Ausdruck wie dog | cat dem schlechter lesbaren dog|cat entspricht, [a b] aber weiterhin auf die Zeichen 'a', 'b' oder ein Leerzeichen passt. Darüber hinaus kannst du Kommentare in einen RE schreiben; Kommentare reichen von einem #-Zeichen bis zum nächsten Zeilenumbruch. Zusammen mit dreifach begrenzten Zeichenketten lassen sich REs damit ordentlicher formatieren:
pat = re.compile(r"""
\s* # Skip leading whitespace
(?P<header>[^:]+) # Header name
\s* : # Whitespace, and a colon
(?P<value>.*?) # The header's value -- *? used to
# lose the following trailing whitespace
\s*$ # Trailing whitespace to end-of-line
""", re.VERBOSE)
Das ist weit besser lesbar als:
pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")
Rückmeldung¶
Reguläre Ausdrücke sind ein kompliziertes Thema. Hat dir dieses Dokument geholfen, sie zu verstehen? Gab es Stellen, die unklar waren, oder Probleme, auf die du gestoßen bist und die hier nicht behandelt werden? Wenn ja, schicke Verbesserungsvorschläge bitte an den Issue-Tracker.
Das vollständigste Buch über reguläre Ausdrücke ist mit großer Sicherheit Jeffrey Friedls „Mastering Regular Expressions“, erschienen bei O’Reilly. Leider widmet es sich ausschließlich den Spielarten regulärer Ausdrücke in Perl und Java und enthält überhaupt kein Python-Material. Als Referenz für das Programmieren in Python ist es daher nicht brauchbar. (Die erste Auflage behandelte Pythons inzwischen entferntes Modul regex, was dir kaum hilft.) Leih es dir vielleicht in deiner Bibliothek aus.