40. Reguläre Ausdrücke III

Back references

Mit [Zahl können Gruppen in den REs selbst referenziert werden. Dabei entspricht [1 der ersten, [2 der zweiten Gruppe usf. r"(.)[1" matcht zum Beispiel zwei gleiche Zeichen, die hintereinander stehen, r"([w+)[s([w+)[s[2[s[1" eine Folge von zwei Wörtern, die gleich dahinter in umgekehrter Reihenfolge kommen.

Besonders praktisch sind die back references in den Ersetzungsstrings von re.sub. Beispiel: Datenbanken werden häufig im CSV-Format ausgetauscht, wobei die einzelnen Felder durch Kommata getrennt sind, etwa für Nachname, Name, Telefonnummer:

Chomsky,Noam,617-555-4543

Eine kleine Funktion, die das ins Format Name, Telefonnummer, Nachname bringt:

def swapFields(ln):
  return re.sub("([^,]*),([^,]*),([^,]*)",
    r"\2,\3,\1", ln)

greedy vs. stingy

Reguläre Ausdrücke sind in der Regel greedy, sie matchen, so viel sie können:

>>> re.match("(.*),(.*)",
  "Chomsky,Noam,617-555-4543").groups()
(’Chomsky,Noam’, ’617-555-4543’)

In unserem Beispiel eben mussten wir deshalb [^,] statt dem simplen Punkt schreiben.

Bequemer sind die stingy Fassungen von * und +, nämlich *? und +?, die matchen, so wenig sie können:

>>> re.match("(.*?),(.*?)",
  "Chomsky,Noam,617-555-4543").groups()
(’Chomsky’, ’’)
>>> re.match("(.*?),(.*?),",
  "Chomsky,Noam,617-555-4543").groups()
(’Chomsky’, ’Noam’)

Kein Licht ohne Schatten. Erstens sind stingy REs nicht besonders schnell (und können in älteren Python-Implementationen bei sehr langen Gruppen zu Überläufen von Pythons Rekursions-Stacks führen), zweitens kann man auch mit ihnen blöde Fehler machen, wie etwa im oberen Beispiel – da hinter dem zweiten (.*?) kein Zeichen mehr kommt, hat Python hier einfach den Leerstring gematcht, nämlich den kleinsten String, der auf den regulären Ausdruck passt.

(Weiterführend:) Wenn ihr tatsächlich CSV-Dateien verarbeiten wollt (und das ist so unwahrscheinlich nicht), solltet ihr das übrigens nicht mit den hier diskutierten rohen Geschichten tun, die etliche Subtilitäten des Formats ignorieren – verwendet stattdessen ab Python 2.3 das csv-Modul.

compile

REs, die öfter gebraucht werden, sollte man kompilieren:

>>> pat = re.compile(",([^,]*)$")
>>> pat.search("Hello, Dolly!").groups()
(’ Dolly!’,)
>>> pat.search("Eins, zwei, drei").groups()
(’ drei’,)

Die Kompilation hat im Wesentlichen zwei Vorteile: Erstens laufen die REs dann schneller, zweitens kann man den regulären Audrücken Namen geben, die auf ihre Funktion verweisen (z.B. nominalPhraseMatcher o.ä.).

Übungen zu diesem Abschnitt

Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen

(1)

Probiert back references aus, wenn euch nichts besseres einfällt, an dem CSV-Beispiel. Was passiert, wenn ihr das r vor den Strings mit den back references vergesst? Lasst euch den resultierenden String einmal mit print und dann aus dem Interpreter-Loop (oder mit der eingebauten Funktion repr) ausgeben.

(2)

Das CSV-Format ist normalerweise noch etwas raffinierter als hier dargestellt; namentlich werden die einzelnen Felder noch in Anführungszeichen gesetzt, etwa so:

"Chomsky", "Noam", "617-555-4543"

Die Idee ist, dass die Abgrenzung der Felder besser wird. Kommata innerhalb der Anführungszeichen werden natürlich ignoriert, und Whitespace sollte nicht relevant sein. Wie würde ein regulärer Ausdruck aussehen, der damit fertig wird? Noch gemeiner wird es, wenn man auch Anführungszeichen innerhalb der Werte zulassen möchte. Üblicherweise werden diese „escaped”, also mit einem Backslash geschützt: [". Wenn man das hinkriegen möchte, werden die regulären Ausdrücke schon ziemlich furchtbar (Tipp: man muss im Wesentlichen backslashes vor dem schließenden Anführungszeichen verbieten).

(3)

Macht euch den Unterschied zwischen greedy und stingy klar, vielleicht zunächst wieder am CSV-Beispiel. Schreibt dann eine Fassung von swapFields, die mit stingy REs arbeitet.

(4)

Bieten stingy REs einen Ausweg aus dem beim „Parsen” von Markupsprachen (XML, HTML) häufigen Problem, dass verschachtelte Elemente falsch erkannt werden, dass also die Größe mindestens eines em-Elements in Ausdrücken wie <em>Dies <em>ist</em> betont</em> falsch berechnet wird?

(5)

Probiert den Effekt von compile. Nehmt dazu einen einigermaßen komplexen regulären Ausdruck (der von swapFields sollte es tun) und führt einige hunderttausend Mal eine Ersetzung durch. Stoppt die Zeit mit und ohne compile. Um den Effekt klarer zu machen, könnt ihr statt einer Ersetzung auch einfach nur ein search laufen lassen.


Markus Demleitner

Copyright Notice