- 1 Section
- 10 Lessons
- unbegrenzt
- Python Fortgeschritten10
- 1.1Module und Pakete: import, pip, venv
- 1.2Objektorientierung in Python: Klassen, __init__, self
- 1.3Vererbung und Mehrfachvererbung in Python
- 1.4Decorators und Generatoren
- 1.5Fehlerbehandlung vertieft: eigene Exceptions
- 1.6Reguläre Ausdrücke mit re
- 1.7Arbeiten mit APIs: requests, JSON
- 1.8Automatisierung mit os, subprocess, pathlib
- 1.9Unit-Tests in Python: pytest
- 1.10Praxisprojekt: Automatisierungsskript mit Tests
Reguläre Ausdrücke mit re
Reguläre Ausdrücke – kurz „Regex" – sind kleine Muster, mit denen du Text durchsuchen, validieren und ersetzen kannst. Eine Mail-Adresse erkennen, alle IP-Adressen aus einer Log-Datei ziehen, ein Datum aus einem String parsen: alles Regex-Aufgaben. Python bringt dafür das eingebaute Modul re mit.
Wenn du die Regex-Grundlagen aus dem Kurs Reguläre Ausdrücke & Textverarbeitung schon kennst, lernst du hier konkret die Python-Seite: die wichtigsten Funktionen von re, kompilierte Patterns, Match-Objekte mit Gruppen, Greedy vs. Lazy, Lookaround, und typische Anwendungen wie E-Mail-, URL- und IP-Validierung.
1) Das re-Modul – Übersicht
Regex-Funktionen in Python sind alle im Modul re. Du importierst es und nutzt seine Funktionen mit zwei Argumenten: zuerst das Muster, dann der zu durchsuchende Text:
import re text = "Anna ist 28 Jahre alt" treffer = re.search(r"\d+", text) print(treffer.group()) # 28
Das r vor dem String (raw string) ist Pflicht-Gewohnheit: Backslashes wie \d werden so nicht von Python als Escape-Sequenzen interpretiert. Ohne r müsstest du jeden Backslash doppelt schreiben – fehleranfällig.
2) Die wichtigsten Funktionen
p irgendwo im Text. Gibt Match-Objekt oder None.ersatz. Wichtigste Methode für Text-Cleanup.str.split.Beispiele dazu:
import re text = "Anna: 28, Ben: 31, Cara: 27" re.search(r"\d+", text).group() # '28' (erster Treffer) re.findall(r"\d+", text) # ['28', '31', '27'] re.sub(r"\d+", "XX", text) # 'Anna: XX, Ben: XX, Cara: XX' re.split(r"[,:]\s*", text) # ['Anna', '28', 'Ben', '31', 'Cara', '27']
3) Zeichenklassen
Eine Zeichenklasse matcht genau ein Zeichen aus einer Menge. Die wichtigsten:
Innerhalb einer Klasse haben viele Sonderzeichen ihre normale Bedeutung – ein Punkt in [.] ist nur ein Punkt, kein „beliebiges Zeichen". Das ist praktisch, wenn du wörtliche Zeichen suchst.
4) Quantifizierer
Ein Quantifizierer sagt, wie oft das vorhergehende Element vorkommen soll:
a* matcht „", „a", „aaa"\d+ matcht „5", „42", „1000"colou?r matcht „color" und „colour"\d{4} matcht „2024" – genau 4 Ziffern\d{2,4} matcht „12", „123", „1234"\d{3,} matcht ab 3 ZiffernBeispiel: deutsche Postleitzahlen (5 Ziffern) finden:
text = "Berlin 10115, München 80331, Köln 50667" plzs = re.findall(r"\d{5}", text) print(plzs) # ['10115', '80331', '50667']
5) Anker – Position im Text
Anker matchen keine Zeichen, sondern Positionen: Wortgrenzen, Zeilenanfang, Zeilenende:
^Hallo matcht nur, wenn Text mit „Hallo" beginnt\.$ matcht abschließenden Punkt\bcat\b matcht „cat" als Wort, nicht in „catch"\Bcat\B matcht „cat" nur, wenn drumherum Wort-ZeichenBeispiel: „Hund" als ganzes Wort finden, nicht in „Hundezucht":
text = "Der Hund läuft. Hundezucht ist Hobby." re.findall(r"\bHund\b", text) # ['Hund'] re.findall(r"Hund", text) # ['Hund', 'Hund'] – auch in „Hundezucht"
6) Gruppen
Gruppen mit runden Klammern (...) tun zwei Dinge: sie fassen einen Teil-Pattern zusammen und merken sich, was gematcht wurde. Das gematchte Stück kannst du später abrufen:
text = "Anna: 28 Jahre" m = re.search(r"(\w+): (\d+)", text) print(m.group()) # 'Anna: 28' – kompletter Match print(m.group(1)) # 'Anna' – erste Gruppe print(m.group(2)) # '28' – zweite Gruppe print(m.groups()) # ('Anna', '28') – alle Gruppen als Tupel
Gruppen sind das wichtigste Werkzeug, um Teile aus einem Match herauszuziehen. Bei findall mit Gruppen bekommst du eine Liste der Gruppen:
text = "Anna: 28, Ben: 31, Cara: 27" re.findall(r"(\w+): (\d+)", text) # [('Anna', '28'), ('Ben', '31'), ('Cara', '27')]
7) Named Groups
Bei mehreren Gruppen werden die Indizes unübersichtlich. Named Groups geben den Gruppen einen Namen:
m = re.search(r"(?P<name>\w+): (?P<alter>\d+)", "Anna: 28") print(m.group("name")) # Anna print(m.group("alter")) # 28 print(m.groupdict()) # {'name': 'Anna', 'alter': '28'}
Die Syntax ist gewöhnungsbedürftig ((?P<name>...)), aber bei komplexen Mustern Gold wert – der Code wird lesbar. Auch in re.sub kannst du mit \g<name> auf benannte Gruppen verweisen.
8) Backreferences
Mit \1, \2 usw. beziehst du dich innerhalb des Patterns auf vorher gematchte Gruppen. Praktisch, um doppelte Wörter zu finden:
text = "Das ist das das Problem" re.findall(r"\b(\w+)\s+\1\b", text, re.IGNORECASE) # ['das'] – findet das wiederholte Wort
Bei Named Groups nutzt du (?P=name):
re.findall(r"\b(?P<w>\w+)\s+(?P=w)\b", text, re.IGNORECASE)
9) Compile für Performance
Wenn du dasselbe Muster oft verwendest, kompiliere es einmal mit re.compile():
plz_pattern = re.compile(r"\b\d{5}\b") for zeile in riesige_datei: if plz_pattern.search(zeile): verarbeite(zeile)
Compile parst das Pattern einmal und liefert ein Pattern-Objekt zurück. Die Methoden (search, findall usw.) sind dieselben wie die re-Modulfunktionen, nur ohne das erste Argument:
p = re.compile(r"\d+") p.search(text) p.findall(text) p.sub("X", text)
Python cached intern auch unkompilierte Patterns – für ein paar Aufrufe brauchst du compile nicht. Bei vielen tausend Wiederholungen lohnt es sich aber, und der Code wird klarer.
10) Flags
Mit Flags steuerst du das Verhalten der Regex-Engine. Häufig:
| Flag | Wirkung |
|---|---|
re.IGNORECASE / re.I | Groß-/Kleinschreibung ignorieren |
re.MULTILINE / re.M | ^ und $ matchen Zeilen, nicht nur String-Grenzen |
re.DOTALL / re.S | . matcht auch Zeilenumbrüche |
re.VERBOSE / re.X | Whitespace und Kommentare im Pattern erlaubt – für Lesbarkeit |
re.findall(r"hund", "Hund und HUND", re.IGNORECASE) # ['Hund', 'HUND'] # Mit VERBOSE-Flag: Pattern mit Kommentaren pattern = re.compile(r""" \b # Wortgrenze \d{5} # Postleitzahl (5 Ziffern) \s+ # Whitespace [A-ZÄÖÜ]\w+ # Stadtname mit Großbuchstabe """, re.VERBOSE)
11) Greedy vs. Lazy
Quantifizierer wie *, +, ? sind standardmäßig gierig (greedy) – sie matchen so viel wie möglich. Mit einem zusätzlichen ? werden sie lazy (faul) – matchen so wenig wie möglich:
<.+>>.Match: <b>fett</b>
<.+?>> auf.Match: <b>, </b>
*?, +?, ??, {n,m}?.text = "<b>fett</b> und <i>kursiv</i>" re.findall(r"<.+>", text) # ['<b>fett</b> und <i>kursiv</i>'] – einmal alles! re.findall(r"<.+?>", text) # ['<b>', '</b>', '<i>', '</i>'] – jedes Tag einzeln
12) Lookahead und Lookbehind
Lookaround ist eine Bedingung, die geprüft wird, ohne dass Zeichen verbraucht werden. Es gibt vier Varianten:
| Syntax | Name | Bedeutung |
|---|---|---|
(?=...) | Positive Lookahead | Es folgt das Muster |
(?!...) | Negative Lookahead | Es folgt NICHT das Muster |
(?<=...) | Positive Lookbehind | Davor steht das Muster |
(?<!...) | Negative Lookbehind | Davor steht NICHT das Muster |
Praktisch, wenn du einen bestimmten Kontext brauchst, ohne ihn ins Match aufzunehmen:
# Zahlen finden, die von "Preis: " gefolgt werden (ohne den Text "Preis: ") text = "Apfel 3, Preis: 12, Birne 5" re.findall(r"(?<=Preis: )\d+", text) # ['12'] # Wörter, denen KEIN "Anti" vorausgeht text = "Antikorruption und Korruption" re.findall(r"(?<!Anti)Korruption", text) # ['Korruption'] # Zahlen, denen ein "€" folgt (€ NICHT im Match) re.findall(r"\d+(?=€)", "12€ 5$ 7€") # ['12', '7']
13) sub mit Funktion oder Gruppen
re.sub ersetzt nicht nur durch einen festen String. Du kannst auf Gruppen verweisen oder eine Funktion übergeben:
# Auf Gruppen mit \1, \2 oder \g<name> verweisen text = "Anna 28, Ben 31" re.sub(r"(\w+) (\d+)", r"\2: \1", text) # '28: Anna, 31: Ben' # Funktion als Ersatz – Zugriff auf das Match-Objekt def verdopple(m): return str(int(m.group()) * 2) re.sub(r"\d+", verdopple, "Anna 28, Ben 31") # 'Anna 56, Ben 62'
Die Funktions-Variante ist mächtig – sie erlaubt komplexe Logik bei jeder Ersetzung. Zum Beispiel können so Werte aus einer Datenbank eingesetzt werden.
14) Typische Anwendungen
Ein paar Patterns, die in der Praxis ständig vorkommen:
# E-Mail (vereinfacht – für strikte Validierung gibt es Bibliotheken) EMAIL = r"^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$" re.fullmatch(EMAIL, "anna@example.de") # Match # URL (vereinfacht) URL = r"https?://[\w.-]+(?:/[\w./?=&%-]*)?" re.findall(URL, "Siehe https://example.de/seite und http://test.com") # IPv4 (jede Oktett-Ziffer ist 0-255 – vereinfacht 0-999) IP = r"\b(?:\d{1,3}\.){3}\d{1,3}\b" re.findall(IP, "Server 192.168.1.1 antwortet, 10.0.0.5 nicht") # ['192.168.1.1', '10.0.0.5'] # Deutsches Datum dd.mm.yyyy DATUM = r"\b(\d{2})\.(\d{2})\.(\d{4})\b" m = re.search(DATUM, "Geboren am 15.03.1996") tag, monat, jahr = m.groups() # Telefonnummer (deutsch, grob) TEL = r"(?:\+49|0)\s?\d{2,5}[\s/-]?\d{3,}" # Log-Zeile parsen LOG = r"^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (.+)$" m = re.match(LOG, "2024-01-15 14:30:01 ERROR Datenbank weg") if m: datum, zeit, level, msg = m.groups()
15) Häufige Fehler
- raw-String vergessen:
"\d"ohner– Python wirft eine Warnung, das Pattern kann silently falsch sein. Immerr"..." - match statt search:
re.matchprüft nur den Anfang. Für „irgendwo im Text" istsearchrichtig - Greedy wo Lazy gemeint war:
.*frisst über mehrere Ziel-Strukturen. Mit.*?nur bis zum nächsten Treffer - findall mit Gruppen liefert Gruppen statt Treffer:
findall(r"(a)(b)", ...)gibt Tupel zurück, nicht „ab". Wenn du den ganzen Match brauchst:finditermitm.group() - Punkt im Pattern matcht alles:
example\.de– Punkt muss escaped werden für „wörtlich Punkt" - Sonderzeichen vergessen zu escapen:
+ * ? . ( ) [ ] { } | \ ^ $haben Sonderbedeutung. Mitre.escape("text")kannst du einen wörtlichen String escapen - Regex für strikte Validierung: gerade bei E-Mail-Adressen ist Regex sehr fehleranfällig. Für Produktiv-Validierung Bibliotheken nutzen, nicht selbst stricken
- Endlos-Loop bei catastrophic backtracking: Pattern wie
(a+)+bauf langen Strings ohnebkann Minuten brauchen. Verschachtelte Quantifizierer vermeiden
html.parser oder BeautifulSoup). Auch JSON, CSV und XML haben eigene, robuste Parser. Regex ist für Text-Patterns ohne strukturierte Verschachtelung.
re.VERBOSE und Kommentaren – dein zukünftiges Ich dankt es dir. Teste neue Patterns interaktiv (z. B. mit der Website regex101.com). Für Validierung lieber fullmatch, für Suche search oder finditer.
Zusammenfassung
Das Python-Modul re bietet alle Regex-Funktionen: search findet das erste Vorkommen, findall alle als Liste, finditer als Iterator über Match-Objekte, sub ersetzt, split splittet. Zeichenklassen wie \d, \w, \s und [abc] matchen ein Zeichen aus einer Menge. Quantifizierer *, +, ?, {n,m} bestimmen die Häufigkeit, mit zusätzlichem ? werden sie lazy statt greedy. Anker wie ^, $ und \b matchen Positionen. Gruppen mit (...) oder Named Groups (?P<name>...) isolieren Teil-Treffer. Lookaround (?=...), (?!...), (?<=...), (?<!...) prüft Kontext ohne ihn zu konsumieren. re.compile beschleunigt wiederholte Anwendung. Always raw strings: r"...". Für HTML/JSON/XML keine Regex, sondern echte Parser.
