Zum Inhalt springen
aviral gupta

// I4.3 · ca. 35 Min. · Aufbau

Reguläre Ausdrücke mit re

Nach dieser Lektion finden, prüfen und zerlegen Sie Text mit regulären Ausdrücken und schreiben ihn mit re.sub um.

Lektion 3 von 6 in I4 Die Standardbibliothek für echte Programme

Danach können Sie

  • search, match oder fullmatch wählen und Muster als Raw-Strings mit \d, \w, [ ], + und {n} schreiben
  • Teile eines Treffers mit Gruppen und benannten Gruppen herausziehen und alle Treffer mit findall sammeln
  • Text mit re.sub umschreiben, mit Gruppenverweisen wie \1 und \g<name>
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen mit den String-Methoden: Was gibt das aus?

    print("2026-09-29".split("-"))
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: Was gibt das aus?

    import re
    
    text = "Order 42 shipped"
    print(re.match(r"\d+", text), re.search(r"\d+", text).group())
  3. Üben · Aufgabe 3 von 7

    Eine deutsche Postleitzahl hat genau fünf Ziffern. Setzen Sie die Funktion ein, damit "123456" und "a12345" abgewiesen werden.

    ok = re.____(r"\d{5}", code) is not None
    ok = re.(r"\d{5}", code) is not None
  4. Üben · Aufgabe 4 von 7

    Was gibt das aus?

    print(len("\n"), len(r"\n"))
  5. Üben · Aufgabe 5 von 7

    Ordnen Sie jedem Muster zu, worauf es passt.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Das Muster hat zwei Gruppen. Was liefert findall?

    import re
    
    print(re.findall(r"(\w+)@(\w+)\.com", "ada@mail.com, bo@web.com"))
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Der Text lautet "Coffee 3.20 EUR, cake 4.50 EUR, juice 2.75 USD". Sammeln Sie mit re.findall und einer Gruppe nur die Beträge in EUR, als Strings wie "3.20". Addieren Sie sie dann und geben Sie die Summe als 7.70 EUR aus.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Ein Log mit benannten Gruppen lesen

Jede Zeile des Logs hat ein Datum, eine Stufe in Großbuchstaben und eine Nachricht. Das Programm prüft jede Zeile mit fullmatch und einem kompilierten Muster mit benannten Gruppen, überspringt Zeilen, die nicht passen, und verbirgt E-Mail-Adressen mit sub. Zum Schluss sammelt findall Zahlen und die Domains der Adressen.

main.py

import re

# One log line: a date, a level in capitals, then the message.
LINE = re.compile(r"(?P<date>\d{4}-\d{2}-\d{2}) (?P<level>[A-Z]+) (?P<message>.*)")
EMAIL = re.compile(r"[\w.]+@[\w.]+\.\w+")

log = """2026-09-28 INFO user ada@example.com logged in
2026-09-28 ERROR disk full on /dev/sda1
not a log line
2026-09-29 WARNING retry 3 of 5 for bo@example.org"""

for line in log.splitlines():
    found = LINE.fullmatch(line)
    if found is None:
        print("skipped:", line)
        continue
    message = EMAIL.sub("<email>", found["message"])
    print(found["date"], found["level"].lower(), "|", message)

print(re.findall(r"\d+", "retry 3 of 5"))
print(sorted(re.findall(r"@([\w.]+)", log)))

Ausführen mit

python main.py

Ausgabe

2026-09-28 info | user <email> logged in
2026-09-28 error | disk full on /dev/sda1
skipped: not a log line
2026-09-29 warning | retry 3 of 5 for <email>
['3', '5']
['example.com', 'example.org']
  • re.compile baut ein Muster einmal; LINE.fullmatch(line) wirkt wie re.fullmatch mit diesem Muster.
  • found["date"] ist dasselbe wie found.group("date").
  • fullmatch liefert None für "not a log line", das if überspringt die Zeile also, bevor eine Gruppe gelesen wird.
  • Mit einer Gruppe hat findall nur die Domains geliefert, ohne das @.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Benutzernamen und Hashtags

Schreiben Sie is_valid_username(name): True, wenn der ganze Name 3 bis 16 Zeichen lang ist, mit einem ASCII-Buchstaben beginnt und sonst nur ASCII-Buchstaben, Ziffern und _ enthält. Schreiben Sie dann hashtags(text): Es liefert die Wörter nach jedem #, ohne das #. "Loving #python and #regex_101!" ergibt ["python", "regex_101"].

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    match prüft nur den Anfang: "ada!" beginnt gut. fullmatch prüft den ganzen Namen.

  2. Hinweis 2

    Zuerst ein Buchstabe, dann 2 bis 15 weitere Zeichen: [A-Za-z][A-Za-z0-9_]{2,15}.

  3. Hinweis 3

    Setzen Sie das Wort in eine Gruppe, r"#(\w+)", dann liefert findall nur die Gruppe.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import re


def is_valid_username(name: str) -> bool:
    return re.fullmatch(r"[A-Za-z][A-Za-z0-9_]{2,15}", name) is not None


def hashtags(text: str) -> list[str]:
    return re.findall(r"#(\w+)", text)


if __name__ == "__main__":
    print(is_valid_username("ada_99"), is_valid_username("ada!"))
    print(hashtags("Loving #python and #regex_101!"))
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

import re


def is_valid_username(name: str) -> bool:
    return re.match(r"[A-Za-z]", name) is not None


def hashtags(text: str) -> list[str]:
    return re.findall(r"#\w+", text)


if __name__ == "__main__":
    print(is_valid_username("ada_99"), is_valid_username("ada!"))
    print(hashtags("Loving #python and #regex_101!"))

test_main.py

from main import hashtags, is_valid_username


def test_valid_names():
    """ada_99 und Bob sind gültige Benutzernamen"""
    got = is_valid_username("ada_99"), is_valid_username("Bob")
    assert got == (True, True), f"für ada_99 und Bob kam {got!r} zurück"


def test_invalid_names():
    """Zu kurz, Ziffer am Anfang, ein ! darin oder 17 Zeichen: alles abgewiesen"""
    names = ["ab", "9lives", "ada!", "a" * 17]
    got = [is_valid_username(name) for name in names]
    assert got == [False, False, False, False], f"für {names!r} kam {got!r} zurück"


def test_hashtags():
    """Hashtags kommen ohne das # zurück"""
    got = hashtags("Loving #python and #regex_101!")
    assert got == ["python", "regex_101"], f"hashtags gab {got!r} zurück"


def test_no_hashtags():
    """Ein Text ohne # ergibt []"""
    got = hashtags("no tags here")
    assert got == [], f"hashtags gab {got!r} zurück"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 2 von 2

Logzeilen zerlegen und Daten umschreiben

Schreiben Sie parse_log_line(line). Für eine ganze Zeile wie "2026-09-28 ERROR disk full" liefert sie {"date": "2026-09-28", "level": "ERROR", "message": "disk full"}; für jede Zeile, die nicht passt (Stufe kleingeschrieben, keine Nachricht, Text vor dem Datum), liefert sie None. Schreiben Sie dann iso_to_german(text): Es schreibt jedes Datum wie 2026-09-30 als 30.09.2026 und lässt den Rest unverändert.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Benennen Sie die Gruppen: (?P<date>\d{4}-\d{2}-\d{2}), dann ein Leerzeichen, (?P<level>[A-Z]+), ein Leerzeichen und (?P<message>.+).

  2. Hinweis 2

    fullmatch weist Text vor dem Datum ab; ist das Ergebnis None, geben Sie None zurück, sonst found.groupdict().

  3. Hinweis 3

    Für die Daten benennen Sie year, month und day und nehmen r"\g<day>.\g<month>.\g<year>" als Ersetzung in re.sub.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import re

LINE = re.compile(r"(?P<date>\d{4}-\d{2}-\d{2}) (?P<level>[A-Z]+) (?P<message>.+)")


def parse_log_line(line: str) -> dict[str, str] | None:
    found = LINE.fullmatch(line)
    if found is None:
        return None
    return found.groupdict()


def iso_to_german(text: str) -> str:
    return re.sub(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})", r"\g<day>.\g<month>.\g<year>", text)


if __name__ == "__main__":
    print(parse_log_line("2026-09-28 ERROR disk full"))
    print(iso_to_german("Due 2026-09-30."))
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

import re


def parse_log_line(line: str) -> dict[str, str] | None:
    # Match the whole line with named groups date, level and message.
    return None


def iso_to_german(text: str) -> str:
    # Rewrite every 2026-09-30 as 30.09.2026.
    return text


if __name__ == "__main__":
    print(parse_log_line("2026-09-28 ERROR disk full"))
    print(iso_to_german("Due 2026-09-30."))

test_main.py

from main import iso_to_german, parse_log_line


def test_parse():
    """Eine Logzeile wird ein dict mit date, level und message"""
    got = parse_log_line("2026-09-28 ERROR disk full on /dev/sda1")
    want = {"date": "2026-09-28", "level": "ERROR", "message": "disk full on /dev/sda1"}
    assert got == want, f"parse_log_line gab {got!r} zurück"


def test_reject():
    """Zeilen, die nicht passen, ergeben None"""
    lines = ["2026-09-28 error disk full", "2026-09-28 INFO", "x 2026-09-28 INFO hi"]
    got = [parse_log_line(line) for line in lines]
    assert got == [None, None, None], f"für {lines!r} gab parse_log_line {got!r} zurück"


def test_german_dates():
    """Jedes Datum wird als Tag.Monat.Jahr geschrieben"""
    got = iso_to_german("from 2026-09-01 to 2026-09-30")
    assert got == "from 01.09.2026 to 30.09.2026", f"iso_to_german gab {got!r} zurück"


def test_no_dates():
    """Text ohne Datum bleibt gleich"""
    got = iso_to_german("no dates here")
    assert got == "no dates here", f"iso_to_german gab {got!r} zurück"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Häufige Fehler

Das Ergebnis nutzen, ohne auf None zu prüfen

import re

found = re.match(r"\d+", "Order 42")
print(found.group())

Was Python ausgibt

AttributeError: 'NoneType' object has no attribute 'group'

Warum, und die Lösung

match, search und fullmatch liefern None, wenn nichts passt, und None hat kein group(). Hier scheitert match, weil der String mit einem Buchstaben beginnt; search fände 42. Prüfen Sie immer mit if found is None:, bevor Sie Gruppen lesen.

Eine nicht geschlossene Klammer

import re

price = re.compile(r"(\d+\.\d{2}")

Was Python ausgibt

re.PatternError: missing ), unterminated subpattern at position 0

Warum, und die Lösung

Jede ( einer Gruppe braucht ihre ). Die Position zeigt, wo die offene Gruppe beginnt. Eine echte Klammer finden Sie mit Backslash: r"\(" und r"\)".

Eine Gruppe abfragen, die es nicht gibt

import re

found = re.search(r"(?P<year>\d{4})-(?P<month>\d{2})", "due 2026-09")
print(found.group("day"))

Was Python ausgibt

IndexError: no such group

Warum, und die Lösung

Das Muster benennt nur year und month, eine Gruppe day gibt es nicht. Vergleichen Sie die Namen in (?P<...>) mit denen, die Sie lesen. Nummerierte Gruppen zählen die öffnenden Klammern von links, ab 1; Gruppe 0 ist der ganze Treffer.

Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Muster und Raw-Strings

Ein Muster beschreibt Text. \d ist eine Ziffer, \w ein Buchstabe, eine Ziffer oder ein Unterstrich, \s Leerraum und . jedes Zeichen außer dem Zeilenumbruch. [A-Z] ist ein Zeichen aus einer Menge. + heißt einmal oder öfter, * keinmal oder öfter, ? optional und {3} genau dreimal. Ein Sonderzeichen selbst finden Sie mit Backslash: \. ist ein echter Punkt. Schreiben Sie Muster als Raw-Strings, r"\d+": In einem normalen String würde Python Escapes wie \b (Rückschritt) selbst auswerten, bevor re sie sieht.

search, match und fullmatch

re.search findet den ersten Treffer irgendwo im String. re.match akzeptiert nur einen Treffer am Anfang, re.fullmatch nur einen, der den ganzen String abdeckt: Das brauchen Sie, um Eingaben zu prüfen. Alle drei liefern ein Match-Objekt oder None, wenn nichts passt. Prüfen Sie also mit if found is None, bevor Sie es benutzen. found.group() ist der gefundene Text, found.span() seine Position.

Gruppen, findall und sub

Klammern bilden eine Gruppe: In r"(\d+)-(\d+)" sind group(1) und group(2) die beiden Zahlen, groups() liefert beide. (?P<year>\d{4}) benennt eine Gruppe: Lesen Sie sie mit found["year"], alle zusammen mit groupdict(). re.findall liefert alle Treffer als Liste: ganze Treffer ohne Gruppen, die Gruppe bei genau einer Gruppe und Tupel bei mehreren. re.sub(pattern, replacement, text) ersetzt jeden Treffer; in der Ersetzung fügt \1 oder \g<year> ein, was eine Gruppe gefunden hat.

Quellen

Zuletzt geprüft am 29. September 2026