Reguläre Ausdrücke für Python

Reguläre Ausdrücke sind eine leistungsfähige Sprache zum Abgleichen von Textmustern. Auf dieser Seite finden Sie eine grundlegende Einführung in reguläre Ausdrücke, die für unsere Python-Übungen ausreichend sind, und zeigt, wie reguläre Ausdrücke in Python funktionieren. Das Python-Modul „re“ unterstützt reguläre Ausdrücke.

In Python wird eine Suche mit regulären Ausdrücken in der Regel so geschrieben:

match = re.search(pat, str)

Die re.search()-Methode verwendet ein Muster eines regulären Ausdrucks und einen String und sucht innerhalb des Strings nach diesem Muster. Wenn die Suche erfolgreich ist, gibt search() ein Übereinstimmungsobjekt oder andernfalls "None" zurück. Daher folgt der Suche normalerweise sofort eine if-Anweisung, um zu testen, ob die Suche erfolgreich war, wie im folgenden Beispiel gezeigt, bei dem nach dem Muster „word:“ gefolgt von einem Wort aus drei Buchstaben gesucht wird (Details siehe unten):

import re

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

Mit dem Code match = re.search(pat, str) wird das Suchergebnis in einer Variablen namens „match“ gespeichert. Die if-Anweisung testet dann die Übereinstimmung. Wenn wahr, war die Suche erfolgreich und "match.group()" ist der übereinstimmende Text (z. B. "word:cat"). Andernfalls ist die Suche fehlgeschlagen und es gibt keinen übereinstimmenden Text, wenn die Übereinstimmung falsch ist (None), um genauer zu werden.

Das „r“ am Anfang des Musterstrings kennzeichnet einen „rohen“ Python-String, der ohne Änderung umgekehrte Schrägstriche durchläuft, was bei regulären Ausdrücken sehr praktisch ist (Java benötigt diese Funktion schlecht!). Ich empfehle Ihnen, Musterzeichenfolgen immer mit dem "r" zu schreiben.

Grundmuster

Der Vorteil von regulären Ausdrücken besteht darin, dass sie Muster angeben können, nicht nur feste Zeichen. Dies sind die einfachsten Muster, die mit einzelnen Zeichen übereinstimmen:

  • a, X, 9, < -- Gewöhnliche Zeichen stimmen genau überein. Meta-Zeichen, die aufgrund ihrer besonderen Bedeutung nicht mit sich selbst übereinstimmen, sind: . ^ $ * + ? { [ ] \ | ( ) (Details siehe unten)
  • . (ein Punkt) – entspricht jedem einzelnen Zeichen außer dem Zeilenumbruch '\n'
  • \w -- (klein geschriebenes w) entspricht einem "Wort"-Zeichen: einem Buchstaben oder einer Ziffer oder einem Unterstrich [a-zA-Z0-9_]. Obwohl „Wort“ die Gedächtnisstütze ist, entspricht es nur einem einzelnen Zeichen, nicht einem ganzen Wort. \W (Großbuchstaben W) entspricht einem beliebigen Zeichen, das kein Wort ist.
  • \b – Grenze zwischen Wort und Nicht-Wort
  • \s -- (kleingeschriebenes s) entspricht einem einzelnen Leerzeichen -- Leerzeichen, Zeilenvorschub, Rückgabe, Tabulator, Form [ \n\r\t\f]. \S (großes S) entspricht allen Zeichen, die kein Leerzeichen sind.
  • \t, \n, \r -- Tabulator, Zeilenvorschub, Return
  • \d -- Dezimalzahl [0-9] (einige ältere Regex-Dienstprogramme unterstützen \d nicht, aber alle \w und \s)
  • ^ = start, $ = end -- entspricht dem Anfang oder Ende des Strings
  • \ – verhindert die Besonderheit eines Zeichens. Verwenden Sie beispielsweise \. für einen Punkt oder \\ für einen Schrägstrich. Wenn Sie sich nicht sicher sind, ob ein Zeichen eine besondere Bedeutung hat, z. B. „@“, können Sie einen Schrägstrich voranstellen, nämlich „\@“. Wenn dies keine gültige Escapesequenz wie „\c“ ist, wird Ihr Python-Programm mit einem Fehler abgebrochen.

Einfache Beispiele

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

Die grundlegenden Regeln der Suche mit regulären Ausdrücken für ein Muster in einem String sind:

  • Die Suche durchläuft den String von Anfang bis Ende und endet bei der ersten gefundenen Übereinstimmung.
  • Alle Muster müssen übereinstimmen, aber nicht der gesamte String
  • Wenn match = re.search(pat, str) erfolgreich ist, ist der Wert nicht „None“ und insbesondere ist „match.group()“ 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

Noch interessanter wird es, wenn Sie mit + und * eine Wiederholung im Muster angeben.

  • + -- 1 oder mehr Vorkommen des Musters links davon, z.B. 'i+' = ein oder mehrere i's
  • * – 0 oder mehr Vorkommen des Musters links davon
  • ? -- stimmt mit 0 oder 1 Vorkommen des Musters links überein

Ganz links & am größten

Zunächst wird die Übereinstimmung ganz links für das Muster ermittelt. Dann wird versucht, einen möglichst großen Teil des Strings zu verwenden, d. h., + und * gehen so weit wie möglich, wobei + und * als "gierig" angesehen werden.

Beispiele für Wiederholungen

  ## 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"

Beispiel für E-Mails

Angenommen, Sie möchten die E-Mail-Adresse in der Zeichenfolge xyz alice-b@google.com lila Affe finden. Wir verwenden dies als Beispiel, um weitere Funktionen für reguläre Ausdrücke zu demonstrieren. Hier ein Versuch mit dem Muster r'\w+@\w+':

  str = 'purple alice-b@google.com monkey dishwasher'
  match = re.search(r'\w+@\w+', str)
  if match:
    print(match.group())  ## 'b@google'

In diesem Fall wird nicht die gesamte E-Mail-Adresse zurückgegeben, da „\w“ nicht mit „-“ oder „.“ in der Adresse übereinstimmt. Dieses Problem beheben wir mithilfe der folgenden Funktionen für reguläre Ausdrücke.

Eckige Klammern

Eckige Klammern können verwendet werden, um eine Gruppe von Zeichen zu kennzeichnen, sodass [abc] mit "a", "b" oder "c" übereinstimmt. Die Codes \w, \s usw. funktionieren auch in eckigen Klammern, mit der einen Ausnahme, dass Punkt (.) einfach für einen wörtlichen Punkt steht. Bei E-Mail-Problemen sind die eckigen Klammern eine einfache Möglichkeit, „.“ und „-“ zu den Zeichen hinzuzufügen, die um das @-Zeichen mit dem Muster r'[\w.-]+@[\w.-]+“ stehen können, um die gesamte E-Mail-Adresse zu erhalten:

  match = re.search(r'[\w.-]+@[\w.-]+', str)
  if match:
    print(match.group())  ## 'alice-b@google.com'
(Weitere Funktionen mit eckigen Klammern) Sie können auch einen Bindestrich verwenden, um einen Bereich anzugeben, sodass [a–z] mit allen Kleinbuchstaben übereinstimmt. Um einen Bindestrich zu verwenden, ohne einen Bereich anzugeben, setzen Sie den Bindestrich zuletzt, z.B. [abc-]. Ein Up-Hut (^) am Anfang einer eckigen Klammer kehrt ihn um, sodass [^ab] alle Zeichen außer "a" oder "b" bedeutet.

Gruppenextraktion

Mit der Gruppenfunktion eines regulären Ausdrucks können Sie Teile des übereinstimmenden Texts auswählen. Angenommen, wir möchten für das E-Mail-Problem den Nutzernamen und den Host separat extrahieren. Fügen Sie dazu Klammern ( ) um den Nutzernamen und den Host im Muster hinzu, zum Beispiel: r'([\w.-]+)@([\w.-]+)'. In diesem Fall ändern die Klammern nicht, was mit dem Muster übereinstimmt, sondern bilden logische "Gruppen" innerhalb des übereinstimmenden Textes. Bei einer erfolgreichen Suche entspricht „match.group(1)“ dem übereinstimmenden Text, der der ersten linken Klammer entspricht, und „match.group(2)“ ist der Text, der der zweiten Klammer links entspricht. Die einfache match.group() ist wie gewohnt der gesamte Übereinstimmungstext.

  str = 'purple alice-b@google.com monkey dishwasher'
  match = re.search(r'([\w.-]+)@([\w.-]+)', str)
  if match:
    print(match.group())   ## 'alice-b@google.com' (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 Workflow bei regulären Ausdrücken besteht darin, dass Sie ein Muster für das, was Sie suchen, schreiben und Klammerngruppen hinzufügen, um die gewünschten Teile zu extrahieren.

finden

„findall()“ ist wahrscheinlich die leistungsstärkste Funktion im Modul „re“. Oben haben wir re.search() verwendet, um die erste Übereinstimmung für ein Muster zu finden. findall() findet *alle* Übereinstimmungen und gibt sie als Liste von Strings zurück, wobei jeder String eine Übereinstimmung darstellt.
  ## Suppose we have a text with many email addresses
  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'

  ## Here re.findall() returns a list of all the found email strings
  emails = re.findall(r'[\w\.-]+@[\w\.-]+', str) ## ['alice@google.com', 'bob@abc.com']
  for email in emails:
    # do something with each found email string
    print(email)

Findall Mit Dateien

Bei Dateien schreiben Sie vielleicht eine Schleife, um über die Zeilen der Datei zu iterieren, und können dann findall() für jede Zeile aufrufen. Lassen Sie stattdessen „findall()“ die Iteration für Sie machen – viel besser! Geben Sie einfach den gesamten Dateitext in „findall()“ ein und geben Sie in einem Schritt eine Liste aller Übereinstimmungen zurück. Denken Sie daran, dass „f.read()“ den gesamten Text einer Datei in einem einzigen String zurückgibt:

  # Open file
  f = open('test.txt', encoding='utf-8')
  # 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 Groups

Der Gruppenmechanismus für Klammern ( ) kann mit findall() kombiniert werden. Wenn das Muster 2 oder mehr Klammerngruppen enthält, gibt „findall()“ anstelle einer Liste von Strings eine Liste mit *Tupeln* zurück. Jedes Tupel stellt eine Übereinstimmung des Musters dar und innerhalb des Tupels befinden sich die Daten aus Gruppe(1), Gruppe(2). Wenn also zwei Gruppen mit Klammern zum E-Mail-Muster hinzugefügt werden, gibt „findall()“ eine Liste von Tupeln zurück, von denen jede 2-mal den Nutzernamen und den Host enthält, z.B. ('alice', 'google.com').

  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
  tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str)
  print(tuples)  ## [('alice', 'google.com'), ('bob', 'abc.com')]
  for tuple in tuples:
    print(tuple[0])  ## username
    print(tuple[1])  ## host

Sobald Sie die Liste der Tupel haben, können Sie sie mit einer Schleife durchlaufen, um für jedes Tupel Berechnungen durchzuführen. Wenn das Muster keine Klammern enthält, gibt „findall()“ eine Liste gefundener Strings zurück, wie in früheren Beispielen. Wenn das Muster ein einzelnes Set von Klammern enthält, gibt „findall()“ eine Liste von Zeichenfolgen zurück, die dieser einen Gruppe entsprechen. (Optionales Feature): Manchmal enthält das Muster Klammern ( ), die Sie jedoch nicht extrahieren möchten. Schreiben Sie in diesem Fall die Klammern mit einem ?: am Anfang, z.B. (?: ), und die linke Klammer zählt nicht als Gruppenergebnis.

RE-Workflow und Fehlerbehebung

Muster für reguläre Ausdrücke enthalten mit nur wenigen Zeichen eine Menge Bedeutung, aber sie sind so dicht, dass Sie viel Zeit damit verbringen können, Ihre Muster zu beheben. Richten Sie Ihre Laufzeit so ein, dass Sie ein Muster ausführen und einfach Übereinstimmungen ausgeben können, indem Sie es z. B. mit einem kleinen Testtext ausführen und das Ergebnis von findall() ausgeben. Wenn das Muster nicht übereinstimmt, versuchen Sie, das Muster zu schwächen und Teile davon zu entfernen, sodass Sie zu viele Übereinstimmungen erhalten. Wenn es keine Übereinstimmungen gibt, können Sie keine Fortschritte machen, da es nichts Konkretes zu prüfen gibt. Wenn zu viele Übereinstimmungen gefunden werden, können Sie die Qualität schrittweise optimieren, um genau das zu erreichen, was Sie erreichen möchten.

Optionen

Für die re-Funktionen sind Optionen erforderlich, um das Verhalten des Musterabgleichs zu ändern. Das Options-Flag wird als zusätzliches Argument zu search() oder findall() usw. hinzugefügt, z.B. re.search(pat, str, re.IGNORECASE).

  • IGNORECASE -- Unterschiede zwischen Groß- und Kleinschreibung werden beim Abgleich ignoriert, sodass "a" sowohl mit "a" als auch mit "A" übereinstimmt.
  • DOTALL -- erlaubt Punkt (.) für neue Zeile. Normalerweise entspricht dies alles außer dem Zeilenumbruch. Dies kann Sie verwirren. Sie meinen, .* stimmt mit allem überein, geht aber standardmäßig nicht über das Ende einer Zeile hinaus. Beachten Sie, dass \s (Leerraum) Zeilenumbrüche enthält. Wenn Sie also eine Reihe von Leerzeichen abgleichen möchten, die möglicherweise eine neue Zeile enthalten, können Sie einfach \s* verwenden.
  • MULTILINE -- Innerhalb einer Zeichenfolge aus vielen Zeilen dürfen ^ und $ dem Anfang und Ende jeder Zeile entsprechen. Normalerweise entspricht ^/$ nur dem Anfang und dem Ende der gesamten Zeichenfolge.

Gierig oder nicht gierig (optional)

Dieser Abschnitt ist optional und zeigt eine erweiterte Technik für reguläre Ausdrücke, die für die Übungen nicht benötigt wird.

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

Angenommen, Sie versuchen, jedes Tag mit dem Muster "(<.*>)" abzugleichen. Womit wird als Erstes abgeglichen?

Das Ergebnis überrascht ein wenig, aber der gierige Aspekt von .* führt dazu, dass <b>foo</b> und <i>so weiter</i> als eine große Übereinstimmung übereinstimmen. Das Problem ist, dass die .* so weit geht, wie es möglich ist, anstatt beim ersten > anzuhalten (auch als "gierig" bezeichnet).

Der reguläre Ausdruck hat eine Erweiterung, bei der Sie ein Fragezeichen (?) am Ende, wie .*? oder .+?, ändern, sodass sie nicht gierig sind. Jetzt hören sie so schnell wie möglich auf. Das Muster "(<.*?>)" erhält also nur "<b>" als erste Übereinstimmung, "</b>" als zweite Übereinstimmung und so weiter, bis jedes <..>-Paar der Reihe nach abgerufen wird. Der Stil besteht in der Regel darin, dass Sie ein .*? verwenden, gefolgt von einer konkreten Markierung (in diesem Fall >), auf die die Ausführung von .*? erzwungen wird.

Die Erweiterung *? stammt von Perl, und reguläre Ausdrücke, die Perl-Erweiterungen enthalten, werden als Perl-kompatible reguläre Ausdrücke (Perl - kompatible reguläre Ausdrücke - pcre) bezeichnet. Python umfasst PCRE-Unterstützung. Viele Befehlszeilen-Dienstprogramme haben ein Flag, mit dem sie PCre-Muster akzeptieren.

Bei einer älteren, aber weit verbreiteten Technik zur Codierung der Idee "alle diese Zeichen außer dem Anhalten bei X" werden eckige Klammern verwendet. Für das obige Beispiel könnten Sie das Muster schreiben, aber statt .*, um alle Zeichen zu erhalten, verwenden Sie [^>]*. Damit werden alle Zeichen übersprungen, die nicht > sind. (Das vorangestellte ^ "invertiert" die eckigen Klammern, sodass es allen Zeichen entspricht, die nicht in den Klammern stehen).

Ersetzung (optional)

Die Funktion re.sub(pat, Replace, str) sucht nach allen Instanzen von „pattern“ in dem angegebenen String und ersetzt sie. Der Ersatzstring kann "\1", "\2" enthalten, die sich auf den Text aus Gruppe(1), Gruppe(2) usw. aus dem ursprünglich übereinstimmenden Text beziehen.

Im folgenden Beispiel wird nach allen E-Mail-Adressen gesucht und die Adressen so geändert, dass der Nutzer (\1) beibehalten wird, aber yo-yo-dyne.com als Host verwendet wird.

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

Training

Informationen zum Üben regulärer Ausdrücke finden Sie in der Übung zu Babynamen.