2.35. Grundlagen von Mustern¶
Ein regulärer Ausdruck ist eine kleine Sprache, um Zeichenketten anhand ihrer Form statt anhand ihres genauen Inhalts zu beschreiben. Verwende ihn, wenn eine Zeichenkette genau dann passt, wenn sie einem Muster folgt, das du beschreiben, aber nicht aufzählen kannst – „eine Folge von Ziffern, gefolgt von einer Einheit“, „eine Zeile, die mit ERROR beginnt und mit einer Zahl endet“, „eine beliebige dieser Dateiendungen, in beliebiger Reihenfolge, mit optionalen v-Präfixen“.
Greife nur dann zu re, wenn eine einfache String-Methode nicht ausreicht.
str.startswith(),str.endswith()– Test auf ein festes Präfix oder Suffix.in– Test, ob eine feste Teilzeichenkette vorhanden ist.str.split(),str.find(),str.replace()– Arbeiten mit festen Trennzeichen.
Jede davon ist schneller, leichter zu lesen und schwerer falsch einzusetzen als das entsprechende Regex. Verwende Regex, wenn die Form der Zeichenkette wichtig ist und die genaue Teilzeichenkette nicht.
2.35.1. Die vier Dinge, die du verwenden wirst¶
Das MicroPython-Modul re stellt vier Dinge bereit:
re.compile()– wandelt eine Musterzeichenkette in ein kompiliertes Musterobjekt um, das du wiederverwenden kannst.re.match()– versucht das Muster am Anfang einer Zeichenkette. Das Muster ist an Position 0 verankert.re.search()– versucht das Muster irgendwo in einer Zeichenkette. Liefert das erste Match.re.sub()– findet jedes Match und ersetzt es.
Bemerkenswerte Auslassungen gegenüber CPython: kein re.findall, kein re.finditer, kein re.split auf Modulebene (kompilierte Muster besitzen stattdessen eine split-Methode), kein re.fullmatch, keine Flag-Konstanten wie re.IGNORECASE. Wo du in CPython zu einer davon greifen würdest, baue das Äquivalent in einer Schleife aus re.search() auf.
2.35.2. Ein erstes Muster¶
Das Muster r'\d+' matcht eine oder mehrere Ziffern:
>>> import re
>>> m = re.search(r'\d+', 'sensor reading 42 ok')
>>> m.group(0)
'42'
Ein paar Dinge, auf die zu achten ist:
Das Muster wird als Rohzeichenkette (
r'...') geschrieben, damit der Backslash in\dreerreicht und nicht als Python-String-Escape verarbeitet wird. Verwende für Regex-Muster immer Rohzeichenketten.re.search()liefert bei Erfolg ein Match-Objekt und bei MisserfolgNone. Prüfe immer, bevor dumatch.group()aufrufst.m.group(0)ist der vollständige Text, auf den das Muster gepasst hat. Gruppe 1, 2, … erscheinen später, sobald das Muster erfassende Klammern enthält.
Dasselbe Muster mit re.match() liefert None, weil die Zeichenkette nicht mit einer Ziffer beginnt:
>>> re.match(r'\d+', 'sensor reading 42 ok') is None
True
>>> re.match(r'\d+', '42 readings')
<match num=1>
2.35.3. Die Bestandteile eines Musters¶
Die meisten nützlichen Muster werden aus einer kleinen Menge von Bestandteilen aufgebaut. Die in MicroPython funktionierenden:
Literale Zeichen – jedes Zeichen, das nicht speziell ist, matcht sich selbst. hello matcht hello.
Sonderzeichen – . ^ $ * + ? { } [ ] \ | ( ) haben alle die unten genannten Bedeutungen. Um eines davon literal zu matchen, maskiere es mit einem Backslash: \. matcht einen literalen Punkt.
Zeichenklassen – Kurzformen für gängige Zeichenmengen:
\d– eine beliebige Ziffer0-9\D– ein beliebiges Nicht-Ziffer-Zeichen\s– ein beliebiges Leerraumzeichen (Leerzeichen, Tabulator, Zeilenumbruch)\S– ein beliebiges Nicht-Leerraumzeichen\w– ein beliebiges „Wort“-Zeichen: Buchstaben, Ziffern, Unterstrich\W– ein beliebiges Nicht-Wortzeichen.– ein beliebiges Zeichen außer Zeilenumbruch
Quantoren – wie oft der vorherige Bestandteil matchen muss:
*– null oder mehr (gierig)+– ein oder mehr (gierig)?– null oder eins{n}– genau n{m,n}– zwischen m und n (einschließlich)
Kombiniert: \d{3}-\d{4} matcht drei Ziffern, einen Bindestrich, vier Ziffern. \s+ matcht ein oder mehr Leerraumzeichen. hello.*world matcht hello, irgendetwas (auch nichts), dann world.
Bemerkung
Gierig bedeutet, dass der Quantor so viel von der Eingabe verbraucht, wie er kann, während der Rest des Musters noch matchen kann. Gegen hello x world y world matcht das .* in hello.*world die längste Folge, die am Ende noch ein world übrig lässt – es erfasst x world y, nicht das kürzere x. Dasselbe gilt für + und die Bereichsform {m,n}: Die Engine nimmt das längste Match, das sie kann, und gibt nur dann nach, wenn der Rest des Musters fehlschlägt.
2.35.4. Ersetzung¶
re.sub() findet jedes Match und ersetzt es durch eine Zeichenkette. Die Ersetzung kann über \1, \2, … auf erfasste Gruppen verweisen (wird später zusammen mit der übrigen Gruppensyntax behandelt). Ohne Gruppen ist re.sub ein einfaches Suchen-und-Ersetzen anhand eines Regex:
>>> re.sub(r'\s+', ' ', 'too many spaces')
'too many spaces'
>>> re.sub(r'\d+', 'N', 'log 12, log 345, log 6')
'log N, log N, log N'
Das dritte Argument ist die Zeichenkette, auf der gearbeitet wird; das Ergebnis ist eine neue Zeichenkette, in der jedes Match ersetzt wurde.
2.35.5. Aufteilen – nur auf einem kompilierten Muster¶
Es gibt kein re.split auf Modulebene. Um anhand eines Regex aufzuteilen, kompiliere zuerst das Muster und rufe dessen split-Methode auf:
>>> sep = re.compile(r'\s*,\s*')
>>> sep.split('a , b,c , d')
['a', 'b', 'c', 'd']
Das optionale zweite Argument begrenzt die Anzahl der Aufteilungen:
>>> sep.split('a, b, c, d', 2)
['a', 'b', 'c, d']
2.35.6. Kompilieren zur Wiederverwendung¶
Wenn dasselbe Muster viele Male ausgeführt wird – innerhalb einer Schleife oder in einer häufig aufgerufenen Funktion – kompiliere es einmal und verwende das kompilierte Objekt wieder:
digit_run = re.compile(r'\d+')
def first_number(line):
m = digit_run.search(line)
return int(m.group(0)) if m else None
Der Aufruf von pattern.match() und pattern.search() auf einem kompilierten Objekt entspricht den Funktionen auf Modulebene, vermeidet aber bei jedem Aufruf die Kosten der erneuten Kompilierung.
2.35.7. Muster, die auf nichts passen¶
Drei Muster im Besonderen bringen Entwickler durcheinander:
.*matcht die leere Zeichenkette.re.search(r'.*', s).group(0)liefert bei jeder Eingabe''.Ein Muster mit einem nicht maskierten Sonderzeichen ist ein Syntaxfehler.
re.compile(r'cost: $5')löst einenValueErroraus, weil$„Ende der Zeichenkette“ bedeutet. Verwender'cost: \$5'.Der Punkt
.matcht keinen Zeilenumbruch. Um über Zeilenumbrüche hinweg zu matchen, schreibe das Muster so, dass es sie explizit mit[\s\S]behandelt, oder gib jeweils eine Zeile auf einmal ein.
Mit diesen Bestandteilen kann ein Muster nahezu jeden festformigen Textabschnitt matchen. Strukturierte Daten wieder aus dem Match herauszuziehen, erfordert Capturing Groups.