Reguläre Ausdrücke sind eine mächtige Sprache, um Textmuster zu finden. Diese Seite gibt eine grundlegende Einführung in reguläre Ausdrücke, die für unsere Python-Übungen ausreicht, und zeigt, wie reguläre Ausdrücke in Python funktionieren. Das Python-Modul „re“ bietet Unterstützung für reguläre Ausdrücke.

In Python wird eine Suche mit regulären Ausdrücken typischerweise wie folgt geschrieben:

 match = re.search(pat, str)

Die Methode re.search() nimmt ein Muster eines regulären Ausdrucks und eine Zeichenkette und sucht nach diesem Muster in der Zeichenkette. Wenn die Suche erfolgreich ist, gibt search() ein Match-Objekt zurück, andernfalls None. Daher folgt auf die Suche in der Regel unmittelbar eine if-Anweisung, um zu prüfen, ob die Suche erfolgreich war, wie im folgenden Beispiel gezeigt, in dem nach dem Muster „word:“ gefolgt von einem Wort mit drei Buchstaben gesucht wird (Einzelheiten siehe unten):

str = 'an example word:cat!!'match = re.search(r'word:\w\w\w', str)# If-statement after search() tests if it succeededif match: print 'found', match.group() ## 'found word:cat'else: print 'did not find'

Der Code match = re.search(pat, str) speichert das Suchergebnis in einer Variablen namens „match“. Dann testet die if-Anweisung die Übereinstimmung – wenn true, war die Suche erfolgreich und match.group() ist der übereinstimmende Text (z.B. ‚word:cat‘). Andernfalls, wenn die Übereinstimmung falsch ist (None, um genauer zu sein), dann war die Suche nicht erfolgreich und es gibt keinen übereinstimmenden Text.

Das ‚r‘ am Anfang der Musterzeichenkette bezeichnet eine Python „rohe“ Zeichenkette, die Backslashes ohne Veränderung durchläuft, was für reguläre Ausdrücke sehr praktisch ist (Java braucht diese Funktion dringend!). Ich empfehle, Musterzeichenfolgen aus Gewohnheit immer mit ‚r‘ zu schreiben.

Grundlegende Muster

Die Stärke regulärer Ausdrücke ist, dass sie Muster und nicht nur feste Zeichen angeben können. Hier sind die grundlegendsten Muster, die mit einzelnen Zeichen übereinstimmen:

  • a, X, 9, < — gewöhnliche Zeichen passen einfach genau. Die Meta-Zeichen, die nicht mit sich selbst übereinstimmen, weil sie besondere Bedeutungen haben, sind: . ^ $ * + ? { \ | ( ) (Details unten)
  • . (ein Punkt) — passt auf jedes einzelne Zeichen außer dem Zeilenumbruch ‚\n‘
  • \w — (Kleinbuchstabe w) passt auf ein „Wort“-Zeichen: einen Buchstaben oder eine Ziffer oder einen Unterstrich . Beachten Sie, dass, obwohl „Wort“ die Eselsbrücke dafür ist, es nur auf ein einzelnes Wortzeichen passt, nicht auf ein ganzes Wort. \W (Großbuchstabe W) passt auf jedes Nicht-Wort-Zeichen.
  • \b — Grenze zwischen Wort und Nicht-Wort
  • \s — (Kleinbuchstabe s) entspricht einem einzelnen Leerzeichen — Leerzeichen, Zeilenumbruch, Return, Tabulator, Formular . \S (Großbuchstabe S) passt auf jedes Nicht-Leerzeichen.
  • \t, \n, \r — Tabulator, Zeilenumbruch, Return
  • \d — Dezimalziffer (einige ältere Regex-Utilities unterstützen nur \d, aber alle unterstützen \w und \s)
  • ^ = Anfang, $ = Ende — passt auf den Anfang oder das Ende der Zeichenkette
  • \ — verhindert die „Besonderheit“ eines Zeichens. Verwenden Sie also zum Beispiel \. für einen Punkt oder \\ für einen Schrägstrich. Wenn Sie sich nicht sicher sind, ob ein Zeichen eine besondere Bedeutung hat, wie z.B. ‚@‘, können Sie einen Schrägstrich davor setzen, \@, um sicherzustellen, dass es nur wie ein Zeichen behandelt wird.

Grundlegende Beispiele

Witz: Wie nennt man ein Schwein mit drei Augen? piiig!

Die Grundregeln der Suche mit regulären Ausdrücken nach einem Muster in einer Zeichenkette lauten:

  • Die Suche durchläuft die Zeichenkette vom Anfang bis zum Ende und hält bei der ersten gefundenen Übereinstimmung an
  • Das gesamte Muster muss übereinstimmen, aber nicht die gesamte Zeichenkette
  • Wenn match = re.search(pat, str) erfolgreich ist, ist match nicht None und insbesondere match.group() ist der übereinstimmende Text
 ## Search for pattern 'iii' in string 'piiig'. ## All of the pattern must match, but it may appear anywhere. ## On success, match.group() is matched text. match = re.search(r'iii', 'piiig') # found, match.group() == "iii" match = re.search(r'igs', 'piiig') # not found, match == None ## . = any char but \n match = re.search(r'..g', 'piiig') # found, match.group() == "iig" ## \d = digit char, \w = word char match = re.search(r'\d\d\d', 'p123g') # found, match.group() == "123" match = re.search(r'\w\w\w', '@@abcd!!') # found, match.group() == "abc"

Wiederholung

Die Sache wird noch interessanter, wenn man + und * verwendet, um Wiederholungen im Muster anzugeben

  • + — 1 oder mehr Vorkommen des Musters links davon, z.B. ‚i+‘ = ein oder mehrere i’s
  • * — 0 oder mehr Vorkommen des Musters links davon
  • ? — 0 oder 1 Vorkommen des Musters auf der linken Seite

Längste & Größte

Zuerst wird die Übereinstimmung auf der linken Seite des Musters gesucht, und dann wird versucht, so viel wie möglich von der Zeichenkette zu verwenden, d.h. + und * gehen so weit wie möglich (+ und * werden als „gierig“ bezeichnet).

Wiederholungsbeispiele

 ## i+ = one or more i's, as many as possible. match = re.search(r'pi+', 'piiig') # found, match.group() == "piii" ## Finds the first/leftmost solution, and within it drives the + ## as far as possible (aka 'leftmost and largest'). ## In this example, note that it does not get to the second set of i's. match = re.search(r'i+', 'piigiiii') # found, match.group() == "ii" ## \s* = zero or more whitespace chars ## Here look for 3 digits, possibly separated by whitespace. match = re.search(r'\d\s*\d\s*\d', 'xx1 2 3xx') # found, match.group() == "1 2 3" match = re.search(r'\d\s*\d\s*\d', 'xx12 3xx') # found, match.group() == "12 3" match = re.search(r'\d\s*\d\s*\d', 'xx123xx') # found, match.group() == "123" ## ^ = matches the start of string, so this fails: match = re.search(r'^b\w+', 'foobar') # not found, match == None ## but without the ^ it succeeds: match = re.search(r'b\w+', 'foobar') # found, match.group() == "bar"

E-Mails Beispiel

Angenommen, Sie wollen die E-Mail-Adresse innerhalb der Zeichenkette „xyz [email protected] purple monkey“ finden. Wir werden dies als laufendes Beispiel verwenden, um weitere Funktionen regulärer Ausdrücke zu demonstrieren. Hier ist ein Versuch mit dem Muster r’\w+@\w+‘:

 str = 'purple [email protected] monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print match.group() ## 'b@google'

Die Suche liefert in diesem Fall nicht die gesamte E-Mail-Adresse, da das \w nicht mit dem ‚-‚ oder ‚.‘ in der Adresse übereinstimmt. Wir werden dies mit den regulären Ausdrücken unten beheben.

Eckige Klammern

Eckige Klammern können verwendet werden, um eine Reihe von Zeichen anzugeben, so dass sie auf ‚a‘ oder ‚b‘ oder ‚c‘ passen. Die Codes \w, \s usw. funktionieren auch innerhalb eckiger Klammern, mit der einzigen Ausnahme, dass der Punkt (.) nur einen buchstäblichen Punkt bedeutet. Für das E-Mail-Problem sind die eckigen Klammern eine einfache Möglichkeit, ‚.‘ und ‚-‚ zu den Zeichen hinzuzufügen, die um das @ herum mit dem Muster r’+@+‘ erscheinen können, um die gesamte E-Mail-Adresse zu erhalten:

 match = re.search(r'+@+', str) if match: print match.group() ## '[email protected]'

(Weitere Funktionen der eckigen Klammern) Sie können auch einen Bindestrich verwenden, um einen Bereich anzugeben, so dass alle Kleinbuchstaben übereinstimmen. Um einen Bindestrich zu verwenden, ohne einen Bereich anzugeben, setzen Sie den Bindestrich an den Schluss, z. B. . Ein Aufwärtshaken (^) am Anfang eines eckigen Klammersatzes invertiert diesen, d.h. er bedeutet jedes Zeichen außer ‚a‘ oder ‚b‘.

Gruppenextraktion

Die „Gruppen“-Funktion eines regulären Ausdrucks ermöglicht es Ihnen, Teile des übereinstimmenden Textes herauszugreifen. Nehmen wir an, dass wir für das E-Mail-Problem den Benutzernamen und den Host getrennt extrahieren wollen. Dazu fügen Sie Klammern ( ) um den Benutzernamen und den Host im Muster hinzu, etwa so: r'(+)@(+)‘. In diesem Fall ändern die Klammern nicht, worauf das Muster zutrifft, sondern sie bilden logische „Gruppen“ innerhalb des Suchtextes. Bei einer erfolgreichen Suche ist match.group(1) der Übereinstimmungstext, der der ersten linken Klammer entspricht, und match.group(2) ist der Text, der der zweiten linken Klammer entspricht. Die einfache match.group() ist immer noch der gesamte Text wie üblich.

 str = 'purple [email protected] monkey dishwasher' match = re.search(r'(+)@(+)', str) if match: print match.group() ## '[email protected]' (the whole match) print match.group(1) ## 'alice-b' (the username, group 1) print match.group(2) ## 'google.com' (the host, group 2)

Ein üblicher Arbeitsablauf mit regulären Ausdrücken ist, dass man ein Muster für das Gesuchte schreibt und Klammergruppen hinzufügt, um die gewünschten Teile zu extrahieren.

findall

findall() ist wahrscheinlich die mächtigste Funktion im re-Modul. Oben haben wir re.search() benutzt, um die erste Übereinstimmung mit einem Muster zu finden. findall() findet *alle* Übereinstimmungen und gibt sie als Liste von Zeichenketten zurück, wobei jede Zeichenkette eine Übereinstimmung darstellt.

 ## Suppose we have a text with many email addresses str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## Here re.findall() returns a list of all the found email strings emails = re.findall(r'+@+', str) ## for email in emails: # do something with each found email string print email

findall With Files

Für Dateien haben Sie vielleicht die Angewohnheit, eine Schleife zu schreiben, um die Zeilen der Datei zu durchlaufen, und Sie könnten dann findall() für jede Zeile aufrufen. Lassen Sie stattdessen findall() die Iteration für Sie erledigen – viel besser! Geben Sie einfach den gesamten Text der Datei in findall() ein und lassen Sie es eine Liste aller Übereinstimmungen in einem einzigen Schritt zurückgeben (erinnern Sie sich daran, dass f.read() den gesamten Text einer Datei in einer einzigen Zeichenkette zurückgibt):

 # Open file f = open('test.txt', 'r') # Feed the file text into findall(); it returns a list of all the found strings strings = re.findall(r'some pattern', f.read())

findall und Gruppen

Der Gruppenmechanismus in Klammern ( ) kann mit findall() kombiniert werden. Wenn das Muster 2 oder mehr Klammergruppen enthält, dann gibt findall() statt einer Liste von Zeichenketten eine Liste von *Tupeln* zurück. Jedes Tupel steht für eine Übereinstimmung mit dem Muster, und innerhalb des Tupels stehen die Daten group(1), group(2) …. Wenn also dem E-Mail-Muster 2 Klammergruppen hinzugefügt werden, gibt findall() eine Liste von Tupeln der Länge 2 zurück, die den Benutzernamen und den Host enthalten, z. B. (‚alice‘, ‚google.com‘).

 str = 'purple [email protected], blah monkey [email protected] blah dishwasher' tuples = re.findall(r'(+)@(+)', str) print tuples ## for tuple in tuples: print tuple ## username print tuple ## host

Wenn Sie die Liste der Tupel haben, können Sie eine Schleife darüber laufen lassen, um einige Berechnungen für jedes Tupel durchzuführen. Wenn das Muster keine Klammern enthält, gibt findall() wie in den früheren Beispielen eine Liste der gefundenen Zeichenfolgen zurück. Enthält das Muster eine einzige Gruppe von Klammern, dann gibt findall() eine Liste von Zeichenketten zurück, die dieser einzelnen Gruppe entsprechen. (Obskure optionale Funktion: Manchmal gibt es Gruppierungen in Klammern ( ) im Muster, die aber nicht extrahiert werden sollen. In diesem Fall schreiben Sie die Paren mit einem ?: am Anfang, z.B. (?: ), und das linke Paren zählt nicht als Gruppenergebnis.)

RE Workflow und Debug

Reguläre Ausdrucksmuster verpacken eine Menge Bedeutung in nur wenige Zeichen, aber sie sind so dicht, dass Sie viel Zeit mit dem Debuggen Ihrer Muster verbringen können. Richten Sie Ihre Laufzeitumgebung so ein, dass Sie ein Muster ausführen und die Übereinstimmungen leicht ausgeben können, z. B. indem Sie es auf einen kleinen Testtext anwenden und das Ergebnis von findall() ausgeben. Wenn das Muster nichts findet, versuchen Sie, das Muster abzuschwächen, indem Sie Teile davon entfernen, so dass Sie zu viele Treffer erhalten. Wenn nichts gefunden wird, können Sie nicht weiterkommen, da es nichts Konkretes zu finden gibt. Wenn es zu viele Übereinstimmungen gibt, kann man es schrittweise verschärfen, um genau das zu erreichen, was man will.

Optionen

Die re-Funktionen nehmen Optionen an, um das Verhalten der Musterübereinstimmung zu ändern. Das Optionsflag wird als zusätzliches Argument zu search() oder findall() usw. hinzugefügt, z.B. re.search(pat, str, re.IGNORECASE).

  • IGNORECASE — ignoriert Unterschiede zwischen Groß- und Kleinschreibung beim Abgleich, so dass ‚a‘ sowohl auf ‚a‘ als auch auf ‚A‘ passt.
  • DOTALL — erlaubt, dass der Punkt (.) mit dem Zeilenumbruch übereinstimmt — normalerweise stimmt er mit allem außer dem Zeilenumbruch überein. Das kann einen verwirren – man denkt, dass .* auf alles passt, aber standardmäßig geht es nicht über das Ende einer Zeile hinaus. Beachten Sie, dass \s (Whitespace) auch Zeilenumbrüche einschließt. Wenn Sie also einen Leerraum abgleichen wollen, der auch einen Zeilenumbruch enthalten kann, können Sie einfach \s*
  • MULTILINE — Innerhalb einer Zeichenkette, die aus mehreren Zeilen besteht, erlauben Sie ^ und $, um den Anfang und das Ende jeder Zeile abzugleichen. Normalerweise würden ^/$ nur auf den Anfang und das Ende der gesamten Zeichenkette passen.

Greedy vs. Non-Greedy (optional)

Dies ist ein optionaler Abschnitt, der eine fortgeschrittene Technik der regulären Ausdrücke zeigt, die für die Übungen nicht benötigt wird.

Angenommen, Sie haben einen Text mit Tags darin: <b>foo</b> und <i>so weiter</i>

Angenommen, Sie versuchen, jedes Tag mit dem Muster ‚(<.*>)‘ abzugleichen – was wird zuerst gefunden?

Das Ergebnis ist etwas überraschend, aber der gierige Aspekt des .* führt dazu, dass das gesamte ‚<b>foo</b> und <i>so on</i>‘ als eine große Übereinstimmung gefunden wird. Das Problem ist, dass das .* so weit geht, wie es kann, anstatt beim ersten > aufzuhören (auch bekannt als „gierig“).

Es gibt eine Erweiterung für reguläre Ausdrücke, bei der man ein ? am Ende hinzufügt, wie z.B. .*? oder .+? und sie so verändert, dass sie nicht gierig sind. Jetzt hören sie auf, sobald sie können. So erhält das Muster ‚(<.*?>)‘ nur ‚<b>‘ als erste Übereinstimmung, und ‚</b>‘ als zweite Übereinstimmung, und so weiter, wobei jedes <..>-Paar der Reihe nach erhalten wird. Typischerweise verwendet man ein .*? und sucht dann direkt rechts davon nach einer konkreten Markierung (in diesem Fall >), die das Ende des .*?-Laufs erzwingt.

Die *?-Erweiterung stammt ursprünglich aus Perl, und reguläre Ausdrücke, die die Erweiterungen von Perl enthalten, sind als Perl-kompatible reguläre Ausdrücke bekannt – pcre. Python enthält pcre-Unterstützung. Viele Kommandozeilenprogramme usw. haben ein Flag, das pcre-Muster akzeptiert.

Eine ältere, aber weit verbreitete Technik, um diese Idee von „alle diese Zeichen, außer dass sie bei X aufhören“ zu kodieren, verwendet den Stil der eckigen Klammern. Für das obige Beispiel könnte man das Muster schreiben, aber statt .*, um alle Zeichen zu erhalten, * verwenden, das alle Zeichen überspringt, die nicht > sind (das führende ^ „invertiert“ den Satz der eckigen Klammern, so dass es auf jedes Zeichen passt, das sich nicht in den Klammern befindet).

Ersetzung (optional)

Die Funktion re.sub(pat, replacement, str) sucht nach allen Instanzen des Musters in der angegebenen Zeichenkette und ersetzt sie. Die Ersetzungszeichenfolge kann ‚\1‘, ‚\2‘ enthalten, die sich auf den Text aus Gruppe(1), Gruppe(2) usw. des ursprünglichen passenden Textes beziehen.

Hier ist ein Beispiel, das nach allen E-Mail-Adressen sucht und sie so ändert, dass der Benutzer (\1) beibehalten wird, aber yo-yo-dyne.com als Host angegeben wird.

 str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## re.sub(pat, replacement, str) -- returns new string with all replacements, ##  is group(1),  group(2) in the replacement print re.sub(r'(+)@(+)', r'@yo-yo-dyne.com', str) ## purple [email protected], blah monkey [email protected] blah dishwasher

Übung

Um reguläre Ausdrücke zu üben, siehe die Übung zu Babynamen.

admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.

lg