Leiter zum Erfolg mit Python

Reguläre Ausdrücke für Fortgeschrittene


In unserer Einführung in reguläre Ausdrücke haben wir die grundlegenden Konzepte der regulären Ausdrücke behandelt. Wir haben gesehen, wie einfachste reguläre Ausdrücke aussehen. Wir haben gelernt, wie man reguläre Ausdrücke in Python mit den Methoden search() und match() aus dem re-Modul benutzt. Außerdem sollten sie damit vertraut sein, wie man Zeichenklassen formuliert und benutzt und was die vorderfinierten Zeichenklassen \d, \D, \s, \S und so weiter bedeuten. Außerdem sollten Sie gelernt haben, wie man mit regulären Ausdrücken den Anfang und das Ende eines Strings matchen kann. Die Sonderbedeutung des Fragezeichens sollte auch bekannt sein, also dass man mittels des Fragezeichens Zeichen und Gruppen optional machen kann. Behandelt wurden außerdem die Quantoren, wie der Plus- und der Sternchen-Quantor.

Außerdem sollten Sie damit vertraut sein, wie man Gruppierungen gestaltet und wie Rückwärtsreferenzen (backreferences) funktionieren. Darüber hinaus hatten wir auch die "match objects" eingeführt und gezeigt, wie man die in Ihnen enthaltene Information mittels der Methoden span(), start(), end() und group() extrahiert.

Die Einführung in die regulären Ausdrücke hatten wir mit umfangreichen Fallbeispielen, Erkennern mit regulären Ausdrücken für deutsche Postleitzahlen und englischen Postcodes beendet.

In diesem Kapitel werden wir fortfahren mit weiteren Möglichkeiten der regulären Ausdrücke und deren syntaktischen Darstellungen. Außerdem werden wir weitere Funktionen des Moduls re vorstellen. Wir werden zeigen, wie man Strings mittels der Methode split() aufspaltet und wie man Teilstrings sucht und durch andere Strings ersetzt.

Alle zutreffenden Teilstrings finden

Python oder besser das Modul re stellt noch eine weitere großartige Methode zur Verfügung, die sich Perl und Java-Programmierer vergeblich wünschen:
re.findall(pattern, string[, flags])
findall liefert alle Übereinstimmungen des RA im String als Liste zurück. (Zur Erinnerung: search() und match liefern nur die erste Übereinstimmung zurück.) Der string wird von links nach rechts gescannt und die Einträge der Liste entsprechen diesem Arbeitsablauf.
>>> t="A fat cat doesn't eat oat but a rat eats bats."
>>> mo = re.findall("[force]at", t)
>>> print mo
['fat', 'cat', 'eat', 'oat', 'rat', 'eat']
Falls eine oder mehrere Gruppen in dem regulären Ausdruck vorkommen, wird eine Liste mit Tuples der einzelnen Gruppenergebnisse zurückgeliefert:
>>> import re
>>> items = re.findall("[0-9]+.*: .*", "Customer number: 232454, Date: February 12, 2011")
>>> print items
['232454, Date: February 12, 2011']
>>> items = re.findall("([0-9]+).*: (.*)", "Customer number: 232454, Date: February 12, 2011")
>>> print items
[('232454', 'February 12, 2011')]
>>> 

Alternativen

In unserer Einführung in reguläre Ausdrücke hatten wir Zeichenklassen eingeführt. Zeichenklassen bieten eine Wahl aus einer Menge von Zeichen. Manchmal benötigt man etwas Analoges für verschiedene Teilausdrücke. Also die Wahl zwischen verschiedenen Ausdrücke. Dazu verwendet man das Symbol "|", da es sich um ein logisches "Oder" handelt.
Im folgenden Beispiel, prüfen wir, ob in einem String die Städte London, Paris, Zürich oder Strasbourg vorkommen und zwar nach einem vorausgegangenen Wort "destination":
 >>> import re
>>> str = "The destination is London!"
>>> mo = re.search(r"destination.*(London|Paris|Zurich|Strasbourg)",str)
>>> if mo: print mo.group()
... 
destination is London
>>> 
Wer das letzte Beispiel für zu künstlich und nicht praxisnah genug hält, für den oder die haben wir hier ein weiteres Beispiel. Nehmen wir an, wir wollen unsere E-Mail filtern. Wir möchten die gesamte Korrespondenz mit Guido van Rossum, dem Schöpfer und Designer von Python, finden. Der folgende reguläre Ausdruck dürfte dann recht hilfreich sein:
r"(^To:|^From:) (Guido|van Rossum)"
Dieser Ausdruck passt auf alle Zeilen die entweder mit 'To:' oder mit 'From:' beginnen und von einem Leerzeichen gefolgt werden, denen dann entweder der Vorname "Guido" oder der Nachname "von Rossum" folgt.

Compilierung von Regulären Ausdrücken

Falls man den selben regulären Ausdruck mehrmals in seinem Skript verwenden will, ist es eine gute Idee, den Ausdruck zu kompilieren.
Die allgemeine Syntax der compile()-Funktion:
re.compile(patter[, flags])
compile lieferte ein regex-Objekt zurück, dass man später zum Suchen und Ersetzen verwenden kann. Das Verhalten des Ausdrucks kann man mit Flag-Werten modifizieren:

AbkürzungAusführlicher NameBeschreibung
re.Ire.IGNORECASEGroß- und Kleinschreibung wird nicht mehr unterschieden
re.Lre.LOCALEDas Verhalten von bestimmten Zeichenklassen, wie z.B. \w, \W, \b,\s, \S wird von der eingestellten Sprachumgebung (locale) abhängig gemacht.
re.Mre.MULTILINE^ und $ passen defaultmäßig nur auf den Anfang und das Ende eines Strings. mit diesem Flag passen sie auch im Innern eines String vor und nach einem Newline "\n".
re.Sre.DOTALL"." passt dann auf alle Zeichen plus dem Newline "\n"
re.Ure.UNICODE\w, \W, \b, \B, \d, \D, \s, \S werden von Unicode-Einstellungen abhängig.
re.Xre.VERBOSEErmöglicht "wortreiche (verbose) reguläre Ausdrücke", d.h. Leerzeichen werden ignoriert. Das bedeutet, dass Leerzeichen, Tabs, Wagenrücklauf "\c" usw. nicht als solche gematcht werden. Wenn man ein Leerzeichen im Verbose-Modus matchen will, muss man es mittels eine Backslashes schützen (escape) oder es in eine Zeichenklasse packen. # wird auch ignoriert, außer, wenn dieses Zeichen in einer Zeichenklasse steht oder hinter einem Backslash steht. Alles hinter einem "#" wird bis zum Ende einer Zeile als Kommentar ignoriert.


Kompilierte reguläre Ausdrücke sparen in der Regel nicht viel Rechenzeit, weil Python sowieso automatisch reguläre Ausdrücke kompiliert und zwischenspeichert, auch wenn man sie mit re.search() oder re.match() aufruft.
Ein guter Grund dafür sie zu verwenden, besteht darin, die Definition und die Benutzung von regulären Ausdrücken zu trennen.

Beispiel

Wir hatten bereits in unserem vorigen Kapitel einen regulären Ausdruck eingeführt, der eine Obermenge der UK-Postcodes matched:
r"[A-z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-HJLNP-UW-Z]{2}"


Mit diesem regulären Ausdruck wollen wir demonstrieren, wie man die Kompilierungsfunktionalität ,,compile'' des re-Modules in einer interaktiven Sitzung nutzen kann: Der obige reguläre Ausdruck wird in einer Variablen "regex" gespeichert und dann kompiliert und das Ergebnis wird in compiled_re abgespeichert. Dann wird auf dem compiled_re-Objekt die Methode search() mit der BBC-Adresse als Argument aufgerufen:
>>> import re
>>> regex = r"[A-z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-HJLNP-UW-Z]{2}"
>>> address = "BBC News Centre, London, W12 7RJ"
>>> compiled_re = re.compile(regex)
>>> res = compiled_re.search(address)
>>> print res
<_sre.SRE_Match object at 0x7fc9f688f6b0>
>>> 

Aufspalten eines Strings mit oder ohne regulärem Ausdruck

Es gibt eine String-Methode split(), mit deren Hilfe man einen String in Teilstringe aufspalten kann.

str.split([sep[, maxsplit]])

Wenn das optionale Argument von split fehlt oder None ist, werden alle Teilstrings, die aus Leerräumen (Whitespaces) bestehen als Separatoren benutzt. Den zweiten Parameter werden wir später besprechen.

Aufspalten eines Strings

Wir demonstrieren dieses Verhalten mit einem Zitat von Abraham Lincoln:
>>> law_courses = "Let reverence for the laws be breathed by every American mother to the lisping babe that prattles on her lap. Let it be taught in schools, in seminaries, and in colleges. Let it be written in primers, spelling books, and in almanacs. Let it be preached from the pulpit, proclaimed in legislative halls, and enforced in the courts of justice. And, in short, let it become the political religion of the nation."
>>> law_courses.split()
['Let', 'reverence', 'for', 'the', 'laws', 'be', 'breathed', 'by', 'every', 'American', 'mother', 'to', 'the', 'lisping', 'babe', 'that', 'prattles', 'on', 'her', 'lap.', 'Let', 'it', 'be', 'taught', 'in', 'schools,', 'in', 'seminaries,', 'and', 'in', 'colleges.', 'Let', 'it', 'be', 'written', 'in', 'primers,', 'spelling', 'books,', 'and', 'in', 'almanacs.', 'Let', 'it', 'be', 'preached', 'from', 'the', 'pulpit,', 'proclaimed', 'in', 'legislative', 'halls,', 'and', 'enforced', 'in', 'the', 'courts', 'of', 'justice.', 'And,', 'in', 'short,', 'let', 'it', 'become', 'the', 'political', 'religion', 'of', 'the', 'nation.']
>>> 

Wir schauen uns nun einen String an, der von seinem Aufbau von Excel oder von Calc (OpenOffice) kommen könnte. In unserem vorigen Beispiel hatten wir gesehen, dass standardmäßig Leerräume (Whitespaces) als Separatoren genommen werden. Im folgenden kleinen Beispiel wollen wir das Semikolon als Separator nehmen. Dazu müssen wir lediglich ";" als Argument beim Aufruf übergeben:
>>> line = "James;Miller;teacher;Perl"
>>> line.split(";")
['James', 'Miller', 'teacher', 'Perl']
Wie bereits erwähnt hat die Methode split() noch einen weiteren optionalen Parameter: maxsplit
Falls maxsplit angegeben wird, dann wird der String an maximal "maxsplit" Separatoren zerlegt, d.h. die Ergebnisliste besteht aus höchstens "maxsplit + 1" Elementen.
Wir demonstrieren die Wirkungsweise von maxsplit mit einer Definition des Begriffs Mammon aus dem "Devil's Dictionary" von Ambrose Bierce:
>>> mammon = "The god of the world's leading religion. The chief temple is in the holy city of New York."
>>> mammon.split(" ",3)
['The', 'god', 'of', "the world's leading religion. The chief temple is in the holy city of New York."]
Wir benutzten ein Leerzeichen als Separator im vorigen Beispiel, was ein Problem darstellen kann, wenn mehrere Leerzeichen (irgendwelche Leerzeichen, also auch Tabs) hintereinander stehen. In diesem Fall wird split() nach jedem Leerzeichen den String aufsplitten und dadurch erhalten wir leere Strings und Strings, die nur ein "\t" enthalten, in unserer Ergebnisliste:
>>> mammon = "The god  \t of the world's leading religion. The chief temple is in the holy city of New York."
>>> mammon.split(" ",5)
['The', 'god', '', '\t', 'of', "the world's leading religion. The chief temple is in the holy city of New York."]
>>> 
Die leeren Strings können wir verhindern, indem wir None als erstes Argument, also für den Separator, benutzen. Dann arbeitet split() mit der Standardeinstellung, also so als wenn man keinen Trennstring angegeben hat:
>>> mammon.split(None,5)
['The', 'god', 'of', 'the', "world's", 'leading religion. The chief temple is in the holy city of New York.']

Split aus dem re-Modul

In den meisten Fällen genügt die split-Methode aus dem str-Modul voll and ganz. Aber wie sieht es beispielsweise aus, wenn man nur die reinen Wörter eines Textes herausfiltern will, d.h. man will alles herausfiltern, was nicht Buchstaben entspricht. Dann benötigen wir eine Split-Funktion, die die Angabe von regulären Ausdrücken zur Definition von Separatoren zulässt. Das Modul re bietet eine solche Methode, die ebenfalls split() heißt. Wir erläutern die die Arbeitsweise mit re.split() in einem kurzen Text, dem Beginn der Metamorphosen von Ovid: :
>>> import re
>>> metamorphoses = "OF bodies chang'd to various forms, I sing: Ye Gods, from whom these miracles did spring, Inspire my numbers with coelestial heat;"
>>> re.split("\W+",metamorphoses)
['OF', 'bodies', 'chang', 'd', 'to', 'various', 'forms', 'I', 'sing', 'Ye', 'Gods', 'from', 'whom', 'these', 'miracles', 'did', 'spring', 'Inspire', 'my', 'numbers', 'with', 'coelestial', 'heat', '']
Seine volle Wirkungskraft entfaltet split im nachfolgenden Beispiel. Als Beispieltext verwenden wir eine ironische Definition von Logik, wie sie Ambrose Bierce in seinem berühmten "Devil's Dictionary" vorschlägt:
# -*- coding: iso-8859-15 -*-

import re

logik = """Logik: Die Kunst des Denkens und Schlussfolgerns in strenger Übereinstimmung mit den Beschränkungen und Unfähigkeiten des menschlichen Missverständnisses. Die Grundlage der Logik ist der Syllogismus (logische Schluss), der aus einem Obersatz, einem Untersatz und einer Konklusion (Schlussfolgerung) besteht - somit
Obersatz: Sechzig Männer können eine Arbeit sechzig mal so schnell machen wie einer.
Untersatz: Ein Mann kann ein Pfostenloch in sechzig Sekunden graben; deswegen
Konklusion: Sechzig Männer können ein Pfostenloch in einer Sekunde graben."""

definitions = re.split("\w+: ", logik)[1:]

print  "Defintion von Logik: \n" + definitions[0]
print  "Beispiel für einen Obersatz: \n" + definitions[1]
print  "Beispiel für einen Untersatz: \n" + definitions[2]
print  "Konklusion: \n" + definitions[3]
Wenn man sich den String logik anschaut, sieht man, dass jede Definition bzw. die Beispiele für den Obersatz und den Untersatz, sowie die Konklusion durch den jeweiligen Begriff gefolgt von einem Doppelpunkt eingeleitet werden. Wir können also als Separator den regulären Ausdruck "\w+:" verwenden.
Den Beispielstring haben wir der Webseite Logik wieder alle Vernunft entnommen. Dort findet man auch das englische Original.
Wir haben auf re.split("\w+: ", logik) den Slice-Operator [1:] angewendet, weil der erste Eintrag der Ergebnisliste von re.split("\w+: ", logik) ein leerer String ist, denn vor dem ersten Separator "Logik:" steht kein Zeichen.

Die Ausgabe des obigen Skriptes sieht wie folgt aus:
Defintion von Logik: 
Die Kunst des Denkens und Schlussfolgerns in strenger Übereinstimmung mit den Beschränkungen und Unfähigkeiten des menschlichen Missverständnisses. Die Grundlage der Logik ist der Syllogismus (logische Schluss), der aus einem Obersatz, einem Untersatz und einer Konklusion (Schlussfolgerung) besteht - somit

Beispiel für einen Obersatz: 
Sechzig Männer können eine Arbeit sechzig mal so schnell machen wie einer.

Beispiel für einen Untersatz: 
Ein Mann kann ein Pfostenlock in sechzig Sekunden graben; deswegen

Konklusion: 
Sechzig Männer können ein Pfostenloch in einer Sekunde graben.

Suchen und Ersetzen mit sub

re.sub(regex, replacement, subject)
Jede Übereinstimmung (match) des regulären Ausdruckes "regex" wird durch den String "replacement" ersetzt.
Beispiel:
>>> import re
>>> str = "yes I said yes I will Yes."
>>> res = re.sub("[yY]es","no", str)
>>> print res
no I said no I will no.