39. Reguläre Ausdrücke II

re.search

gibt match objects zurück (ebenso übrigens das verwandte re.match, das von vorneherein nur nach Matches am Anfang des Strings sucht, so dass man re.match(regexp, s) fast mit re.search("^"+regexp, s) identifizieren kann). Diese Objekte haben unter anderem folgende Methoden:

  • start([group]) – Gibt die Position des Anfangs des Matches zurück; group ist, wenn nicht angegeben, 0, wodurch der ganze Match angesprochen wird, kann aber auch eine der mit Klammern definierten Gruppen ansprechen.
  • end([group]) – Wie start, nur wird die Endposition zurückgegeben
  • group([group1,...]) – gibt den Inhalt der Gruppe(n) zurück, 0 als Argument heißt wieder ganzer Match.
>>> mat = re.search(r'(\w+)\s*->\s*"([^"]*)"',
...     ' bla->  "gaga"')
>>> mat.start(1), mat.end(1)
(1, 4)
>>> mat.group(1), mat.group(2)
('bla', 'gaga')
>>> mat.group(0)
'bla->  "gaga"'
>>> mat.group(1,2)
('bla', 'gaga')

[w und [s sind Character Classes sie stehen für “Alle Buchstaben” bzw. “Alles, was ein Leerzeichen sein kann”. Es gibt noch etliche andere character classes dieser Art, vgl. Dokumentation des re-Moduls.

Wir verwenden hier außerdem raw strings – ihr erinnert euch, dass in raw strings keine Escapesequenzen ausgewertet werden, und das ist genau das, was wir hier haben wollen: Die Maschinerie von Regulären Ausdrücken muss das [w sehen, und deshalb sollte es besser nicht bereits von Pythons Maschinerie zur Bewertung von String-Literalen gefressen werden. Nebenbei: Für [w würde das ohnehin nicht passieren, weil es für Python selbst keine Bedeutung hat, für Sequenzen wie [1 (die wir bald kennenlernen werden) aber sehr wohl, und drum ist es das beste, sich gleich anzugewöhnen, reguläre Ausdrücke in raw strings zu halten.

REs und Encodings

Vorteil der Verwendung dieser Character Classes ist unter anderem, dass das Programm mit relativ wenig Aufwand zu lokalisieren ist, d.h. etwa auf die verschiedenen Definitionen von “Buchstabe” in verschiedenen Sprachen anzupassen. Im Deutschen kämen etwa die Umlaute zu [w, im Französischen etliche Zeichen mit allerlei Akzenten usf.

Leider konnten alte Fassungen der Bibliothek, die die regulären Ausdrücke bewertet, nicht mit verschiedenen Encodings und den so genannten Locales (die unter anderem bestimmen, was eigentlich alles Buchstaben sind) umgehen. Als dann die Locale-Unterstützung kam, wollten die Python-MacherInnen das bestehende Verhalten nicht ändern – der Fachbegriff dafür ist Abwärtskompatibilität, eine neue Version eines Programms verhält sich in einer bestimmten Beziehung wie die vorherigen. Aus diesem Grund muss man explizit bestellen, dass Dinge wie [w wissen soll, was für eine bestimmte Sprache Zeichen sind und was nicht.

Dafür gibt es zwei Möglichkeiten: entweder verwendet ihr das unten diskutierte re.compile und gebt als zweites Argument der Funktion re.L, oder ihr schreibt ein (?L) an den Anfang der RE (was in der Regel einfacher ist) – "(?L)[w+" würde also auf alle Wörter (definiert als zusammenhängende Sequenzen von Buchstaben einer Sprache) passen.

Ganz reicht das aber noch nicht, weil ihr dem System ja mitteilen müsst, welche Sprache ihr behandeln wollt. Wie das geht, sehen wir im Kapitel zu locales.

sub

Eine weitere ausgesprochen nützliche Funktion, die im re-Modul enthalten ist, ist sub. Damit können alle (oder manche) Matches eines regulären Ausdrucks durch etwas anderes ersetzt werden. Eine Funktion, die aus einem Text alle HTML-Tags entfernt:

import re

def htmlToTxt(html):
  return re.sub("<[^>]+>", "", html)

Sowas könnte etwa so verwendet werden:

if __name__=="__main__":
  import sys
  for inName in sys.argv[1:]:
    translated = htmlToTxt(open(inName).read())
    outF = open(inName+".txt", "w")
    outF.write(translated)
    outF.close()

Das zweite Argument zu sub kann auch eine Funktion sein. Diese Funktion nimmt ein Match-Objekt und sollte einen String zurückgeben. Der Effekt ist, dass ein Match durch den von der Funktion erzeugten String ersetzt wird, dass wir also “berechnete Ersetzungen” haben.

Ein paar Beispiele dazu:

>>> import re
>>> def identity(match):
...     return match.group()
...
>>> def reverse(match):
...     stuff = list(match.group())
...     stuff.reverse()
...     return "".join(stuff)
...
>>> from htmlentitydefs import entitydefs
>>> def entityToLatin1(match):
...     return entitydefs.get(match.group(1), match.group())
...
>>> re.sub("..", identity, "blabla")
'blabla'
>>> re.sub("..", reverse, "blabla")
'lbbaal'
>>> re.sub("...", reverse, "blabla")
'albalb'
>>> re.sub("....", reverse, "blabla")
'balbla'
>>> re.sub("&([^;]+);", entityToLatin1, "Gr&uuml;&szlig;e")
'Gr\xfc\xdfe'

Hier haben wir zunächst drei Funktionen definiert. Die erste, identity, nimmt ein Matchobjekt und gibt den kompletten Match zurück – hier wird also der Match durch den Match ersetzt, was in Summe keine Veränderung gibt.

Amusanter ist schon die zweite Funktion: sie gibt den Match verdreht zurück. Dazu wandelt sie den String zwischenzeitlich in eine Liste, was immer empfohlen ist, wenn am einen String “als Sequenz”, also durch Zugriff auf die einzelnen Elemente, verändern möchte. Die Konsequenz davon ist unten in den Beispielen zu sehen.

Die letzte Funktion tut schon etwas beinahe Nützliches. HTML nämlich ist, auch, wenn der Text selbst allerlei nationale Sonderzeichen enthält, die auf US-Tastaturen nicht draufstehen, ausschließlich mit Zeichen aus ASCII schreibbar. Dazu gibt es die “Entities”, bestehend aus einem Ampersand, ein paar Buchstaben, die beschreiben, welches Zeichen gemeint sei, und dann einem Strichpunkt. So könnt ihr auf HTML-Seiten &auml; schreiben und bekommt einen a-umlaut, also ein ä, während &Auml; auf ein Ä und &szlig; (“s-z-Ligatur”) auf das ß führt. Nebenbei: Hier haben sich die Namensgeber ordentlich vertan, denn das scharfe s ist historisch mitnichten eine Ligatur aus s und z (auch wenn die Straßenschilder in Berlin das nachträglich so behaupten), sondern eine aus s und s.

Für den heute noch üblichen ISO-Latin1-Zeichensatz enthält das Python-Modul htmlentitydefs ein Dictionary enititydefs, das für viele Zeichenfolgen auf den passenden Ersatztext führt – auml geht auf ä, uuml geht auf ü, mu geht auf μ. Und genau das nutzt entityToLatin1 aus, um HTML-Texte mit Entities so gut wie möglich in Latin-1-Text (der z.B. einfacher mit regulären Ausdrücken zu bearbeiten ist) verwandelt.


Markus Demleitner

Copyright Notice