Zum Inhalt
wannacode

Reguläre Ausdrücke

Kurz gesagt: Ein regulärer Ausdruck (Regex) ist ein Muster, das beschreibt, wie ein Text aussieht: Damit prüfst, durchsuchst, ersetzt und zerlegst du Text, statt viele ifs zu schreiben.

Problem

Zehn Zeilen für ein Datum

Das Buchungsformular will ein Datum in der Form tt.mm.jjjj. Du prüfst es Zeichen für Zeichen:

def is_date(s):
if len(s) != 10:
return False
for i in range(len(s)):
c = s[i]
if i == 2 or i == 5:
if c != ".":
return False
elif c < "0" or c > "9":
return False
return True
print(is_date("05.03.2026"))
print(is_date("5.3.2026"))
print(is_date("05/03/2026"))
Ausgabe
True
False
False

Zehn Zeilen, und die Funktion prüft nur das Format: zwei Ziffern, ein Punkt, zwei Ziffern, ein Punkt, vier Ziffern. Wenn du auch / statt eines Punkts annehmen willst oder einen Tag mit einer Ziffer, kommen noch einmal so viele Zeilen dazu. Eine Telefonnummer, eine E-Mail oder ein Kfz-Kennzeichen kosten dich jeweils noch so eine Funktion.

Die Idee

Ein Muster statt Code

Du beschreibst, wie der Text aussieht, und Python übernimmt die Prüfung:

import re
def is_date(s):
return re.fullmatch(r"\d{2}\.\d{2}\.\d{4}", s) is not None
print(is_date("05.03.2026"))
print(is_date("5.3.2026"))
print(is_date("05/03/2026"))
Ausgabe
True
False
False

Du liest das Muster von links nach rechts: \d ist eine Ziffer, {2} heißt „zweimal“, \. ist ein Punkt. So ein Muster heißt regulärer Ausdruck (regular expression, Regex).

Reguläre Ausdrücke liegen im Modul re, deshalb beginnt die Datei mit import re. re.fullmatch(muster, text) prüft, ob der ganze Text zum Muster passt, vom ersten bis zum letzten Zeichen. Wenn ja, gibt es den Treffer zurück, wenn nein, None. Deshalb ergibt is not None den Wert True oder False.

Warum steht ein r vor den Anführungszeichen? Der Backslash ist auch für einen gewöhnlichen Python-String besonders. Mit r ist der String roh (Raw-String), und \ darin ist einfach \. Das sehen wir uns gleich genauer an. Schreib vorerst jedes Muster mit r.

Sieh es dir an

Wie das Muster sucht

re.sub prüft nicht den ganzen Text. Es sucht die Stücke, die zum Muster passen, und ersetzt sie. So geht es die Zeile entlang:

import re
line = "120 € am 05.03.2026, Frist 15.04.2026"
masked = re.sub(r"\d{2}\.\d{2}\.\d{4}", "[Datum]", line)
print(masked)
Schritt für Schritt, in Worten
  1. Die Zeile ist eine Folge von Zeichen, jedes mit seiner eigenen Position.
  2. Ab Position 0: „1“ und „2“ sind Ziffern, aber dann kommt „0“ statt eines Punkts. Der Versuch scheitert.
  3. Ein neuer Versuch ab jedem nächsten Zeichen: „20 “, „0 “, „ €“… alles Fehlschläge.
  4. Ab Position 9 passen alle zehn Teile: der erste Treffer.
  5. Die Suche geht nach dem Treffer weiter, ab dem Komma, und findet den zweiten.
  6. re.sub ersetzt beide Treffer durch „[Datum]“.

Das Muster wird der Reihe nach ab jeder Position probiert. Sobald ein Teil nicht passt, scheitert der Versuch, und ein neuer beginnt beim nächsten Zeichen. Deshalb ist 120 nicht durchgekommen: zwei Ziffern, und dann 0 statt eines Punkts. Wenn es einen Treffer findet, geht die Suche danach weiter.

Die Idee

Zeichenklassen

In eckigen Klammern zählst du auf, welche Zeichen an einer Stelle erlaubt sind. Ein bulgarisches Kfz-Kennzeichen besteht aus einem oder zwei Buchstaben, vier Ziffern und zwei Buchstaben:

import re
plates = ["CA1234BX", "B7788KK", "CA12345BX", "ca1234bx"]
for plate in plates:
print(plate, re.fullmatch(r"[A-Z]{1,2}\d{4}[A-Z]{2}", plate) is not None)
Ausgabe
CA1234BX True
B7788KK True
CA12345BX False
ca1234bx False
Muster Trifft ein Zeichen
[ABE] A, B oder E
[0-9], [A-Z] aus dem Bereich: eine Ziffer, ein lateinischer Großbuchstabe
[^0-9] alles außer einer Ziffer (^ am Anfang der Klammern)
\d eine Ziffer, dasselbe wie [0-9]
\s ein Leerzeichen, ein Tabulator, ein Zeilenumbruch
\w ein Buchstabe aus jeder Sprache, eine Ziffer oder _
. irgendein Zeichen (außer einem Zeilenumbruch)

Großbuchstaben drehen die Bedeutung um: \D ist „keine Ziffer“, \S ist „kein Leerzeichen“, \W ist „nicht \w“.

In Python kennt \w auch Buchstaben wie „ü“ und „ß“. Mit dem Flag re.ASCII (dem dritten Argument von fullmatch) werden es nur lateinische Buchstaben, wie in Java und JavaScript:

import re
print(re.fullmatch(r"\w+", "Berlin") is not None)
print(re.fullmatch(r"\w+", "München") is not None)
print(re.fullmatch(r"\w+", "München", re.ASCII) is not None)
print(re.fullmatch(r"[A-Za-zü]+", "München") is not None)
Ausgabe
True
True
False
True
Genauer, für Neugierige
  • [A-Z] ist ein Bereich nach den Codes der Zeichen. Er enthält nur die 26 lateinischen Buchstaben, ohne „Ä“, „Ö“, „Ü“ oder „ß“, die woanders in der Tabelle stehen. Für deutschen Text reicht [A-Za-z] deshalb nicht. Füge sie hinzu: [A-Za-zÄÖÜäöüß].
  • Echte bulgarische Kennzeichen verwenden nur 12 Buchstaben, die in Kyrillisch und Lateinisch gleich aussehen: [ABEKMHOPCTYX].

Probier es aus

Der Punkt

Jemand hat den Backslash vor den Punkten vergessen:

Was gibt das Programm aus?

import re
pattern = r"\d{2}.\d{2}.\d{4}"
print(re.fullmatch(pattern, "05.03.2026") is not None)
print(re.fullmatch(pattern, "05-03-2026") is not None)
print(re.fullmatch(pattern, "05.03.26") is not None)
Antwort

Die Idee

Wie oft

Nach einem Zeichen oder einer Klasse sagst du, wie oft es sich wiederholen darf. Das ist ein Quantifizierer:

Quantifizierer Bedeutet
? null- oder einmal (es kann da sein oder nicht)
* null- oder mehrmals
+ ein- oder mehrmals
{4} genau 4-mal
{1,2} 1- bis 2-mal
{2,} 2-mal oder öfter

Eine bulgarische Handynummer beginnt mit 0 oder +359, dann kommt 87, 88 oder 89 und noch 7 Ziffern:

import re
mobile = r"(\+359|0)8[789]\d{7}"
print(re.fullmatch(mobile, "0888123456") is not None)
print(re.fullmatch(mobile, "+359877654321") is not None)
print(re.fullmatch(mobile, "0888 123 456") is not None)
print(re.fullmatch(mobile, "088812345") is not None)
Ausgabe
True
True
False
False

| heißt „oder“. Die Klammern halten \+359|0 zusammen: Ohne sie würde das „oder“ das ganze Muster in zwei Teile spalten. Klammern können noch mehr, das sehen wir in der nächsten Lektion. Das + am Anfang hat einen \, weil es sonst ein Quantifizierer ist.

Ein Quantifizierer wirkt auch auf eine ganze Gruppe in Klammern. Hier ist ein Preis mit oder ohne Nachkommateil:

import re
price = r"\d+([.,]\d{1,2})? €"
print(re.fullmatch(price, "12 €") is not None)
print(re.fullmatch(price, "12.5 €") is not None)
print(re.fullmatch(price, "12,50 €") is not None)
print(re.fullmatch(price, ".50 €") is not None)
Ausgabe
True
True
True
False

([.,]\d{1,2})? heißt „ein Punkt oder ein Komma und eine oder zwei Ziffern“, und dieser ganze Block darf fehlen.

Die Idee

Raw-String: r"…"

Das Muster geht durch zwei Schichten. Zuerst liest Python den String in den Anführungszeichen, dann liest re das Muster.

In einem gewöhnlichen String beginnt \ ein Sonderzeichen: \n ist ein Zeilenumbruch, \" ist ein Anführungszeichen. \d gehört nicht dazu. Python 3.12 warnt dich, lässt \d aber so, wie es ist, und das Programm funktioniert:

import re
date = "05.03.2026"
print(re.fullmatch("\d{2}\.\d{2}\.\d{4}", date) is not None)
Die Konsole
/shop/date.py:4: SyntaxWarning: invalid escape sequence '\d'
print(re.fullmatch("\d{2}\.\d{2}\.\d{4}", date) is not None)
True

Es funktioniert aus Glück. \b zum Beispiel ist ein Sonderzeichen in einem String („Backspace“), und ein Muster damit, aber ohne r, sucht still etwas anderes. Das siehst du gleich. Deshalb schreibt man Muster als Raw-Strings: Mit r vor den Anführungszeichen ist \ einfach \:

import re
date = "05.03.2026"
print(re.fullmatch(r"\d{2}\.\d{2}\.\d{4}", date) is not None)
print(re.fullmatch(r"C:\\Temp", "C:\\Temp") is not None)
Ausgabe
True
True
Du willst im Muster Du schreibst in Python Ohne r
\d (eine Ziffer) r"\d" "\\d"
\. (einen echten Punkt) r"\." "\\."
\\ (einen echten Backslash) r"\\" "\\\\"

Die Zeichen, die einen \ brauchen, wenn du sie wörtlich willst, sind . * + ? ( ) [ ] { } | ^ $ \.

Probier es aus

Finde den Fehler

Das Programm soll „12 € und 50 Cent“ ausgeben, aber es bricht so ab:
Fehler
Traceback (most recent call last):
File "/shop/price.py", line 5, in <module>
euros = int(parts[0])
^^^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: ''

Wähle die Zeile mit dem Fehler.

price = "12.50"
print(price.split("."))
Ausgabe
['12', '50']

Die Idee

Ersetzen und Zerlegen nach einem Muster

re.sub(muster, ersatz, text) und re.split(muster, text) arbeiten mit einem Muster. So räumst du Text auf, den Leute eintippen, wie es ihnen gerade passt, und zerlegst ihn:

import re
address = "Lieferung: Hauptstraße 12, Eingang B"
print(re.sub(r"\s+", " ", address))
items = re.split(r"[,;\s]+", "Brot, Milch;Kaffee Tee")
print(f"{len(items)}: " + " | ".join(items))
print(re.sub(r"(?i)summer\d+", "***", "Code SUMMER10, Code summer25"))
Ausgabe
Lieferung: Hauptstraße 12, Eingang B
4: Brot | Milch | Kaffee | Tee
Code ***, Code ***
  • \s+ ist „ein oder mehrere Leerzeichen“: Jede Gruppe von Leerzeichen wird zu einem.
  • [,;\s]+ ist „Kommas, Semikolons oder Leerzeichen, egal wie viele“: Das Trennzeichen kann jedes davon sein.
  • (?i) am Anfang ist ein Flag: Das Muster achtet nicht auf Groß- und Kleinschreibung.

Die String-Methode replace ersetzt auch jedes Vorkommen, sucht aber nach gewöhnlichem Text, nicht nach einem Muster. Wenn du kein Muster brauchst, nimm sie: Dann musst du nicht an Punkte und Backslashes denken.

\b ist eine Wortgrenze: die Stelle zwischen einem Buchstaben und etwas, das kein Buchstabe ist. Damit ersetzt du nur ganze Wörter. In Python kennt \b Umlaute wie „Ö“, aber das Muster muss ein Raw-String sein:

import re
text = "Öl und Öle"
print(re.sub("Öl", "Fett", text))
print(re.sub("\bÖl\b", "Fett", text))
print(re.sub(r"\bÖl\b", "Fett", text))
Ausgabe
Fett und Fette
Öl und Öle
Fett und Öle

Ohne \b wird aus „Öle“ „Fette“. Die zweite Zeile hat kein r: Dort ist \b das Backspace-Zeichen, keine Grenze, also wird nichts ersetzt, und es gibt keinen Fehler. Erst mit dem Raw-String in der dritten Zeile wird nur das Wort „Öl“ ersetzt.

Genauer, für Neugierige
  • \B ist das Gegenteil von \b: eine Stelle innerhalb eines Wortes. r"\Bö" findet „ö“ nur, wenn es nicht am Anfang eines Wortes steht: in „schön“, aber nicht in „öde“.
  • ^ ist der Anfang des Textes und $ das Ende: re.sub(r"^0+", "", text) entfernt die führenden Nullen. Mit fullmatch brauchst du sie nicht, weil es sowieso den ganzen Text ansieht.
  • Du kannst die Flags auch als Konstanten angeben: re.IGNORECASE ist dasselbe wie (?i) und re.ASCII wie (?a). Du übergibst sie als letztes Argument: re.sub(muster, ersatz, text, flags=re.IGNORECASE).
  • re.split("|", "a|b") ist eine Falle wie der Punkt: | ist „oder“, und das Muster ist leer, also trennt es zwischen je zwei Zeichen. Die String-Methode "a|b".split("|") sucht nach gewöhnlichem Text und hat dieses Problem nicht.
import re
print(re.sub(r"\Bö", "o", "schön öde"))
print(re.fullmatch(r"(?a)\w+", "München"), re.fullmatch(r"(?i)öl", "ÖL") is not None)
print(re.split("|", "a|b"))
print("a|b".split("|"))
Ausgabe
schon öde
None True
['', 'a', '|', 'b', '']
['a', 'b']

Probier es aus

Postleitzahl

Eine bulgarische Postleitzahl hat genau vier Ziffern. Was fehlt, damit das Programm für "1000" True ausgibt und für "100" oder "10000" False?

import re

code = "1000"
print(re.fullmatch(r"", code) is not None)

Übung

Prüfungen mit einem Muster

Löse die Aufgaben in deinem Editor. Lies Zeilen, solange es welche gibt: for line in sys.stdin:, mit import sys ganz oben. Jede Zeile endet mit "\n", also entferne es mit strip() oder rstrip("\n"). In der Lektion „Dateien“ liest du dieselben Zeilen aus einer Datei.

1. Datum. Prüfe, ob jedes Datum in der Form tt/mm/jjjj ist. Der Tag geht von 01 bis 31, der Monat von 01 bis 12. Tipp: Der Tag ist 0[1-9] oder [12]\d oder 3[01].

Eingabe
01/01/2020
32/01/2020
15/05/2021
Ausgabe
01/01/2020 - Gültig
32/01/2020 - Ungültig
15/05/2021 - Gültig

2. Handynummer. Leute schreiben Nummern mit Leerzeichen und Bindestrichen. Entferne sie mit re.sub und prüfe, was übrig bleibt, mit dem Muster für Handynummern aus der Lektion.

Eingabe
0888 123 456
+359 87 765 4321
02 812 3456
0899-12-345
Ausgabe
0888 123 456 - gültig
+359 87 765 4321 - gültig
02 812 3456 - ungültig
0899-12-345 - ungültig

3. E-Mail. Prüfe, ob jede Zeile wie eine E-Mail aussieht: ein oder mehrere lateinische Buchstaben, Ziffern, ., + oder -, dann @, dann ein Domainname mit mindestens einem Punkt (firma.de, mail.firma.de). Hier würde \w auch Buchstaben wie „ü“ durchlassen: Zähl die lateinischen Buchstaben mit [A-Za-z0-9_] auf.

Eingabe
maria.weber@firma.de
maria@firma
@firma.de
maria weber@firma.de
Ausgabe
maria.weber@firma.de - ja
maria@firma - nein
@firma.de - nein
maria weber@firma.de - nein

4. Einkaufsliste. Die Produkte sind durch Kommas, Semikolons, senkrechte Striche oder Leerzeichen getrennt, manchmal durch mehrere auf einmal. Gib sie nummeriert aus.

Eingabe
Brot, Milch;Kaffee Tee|Käse
Ausgabe
1. Brot
2. Milch
3. Kaffee
4. Tee
5. Käse
Genauer, für Neugierige
  • Das Muster für das Datum nimmt auch 31/02/2026 an. Ob es den Tag im Monat gibt, kann eine Regex nicht vernünftig sagen. Dafür gibt es das Modul datetime, und das Muster ist nur das erste Sieb:
import datetime
try:
datetime.date(2026, 2, 31)
except ValueError as e:
print(e)
Ausgabe
day is out of range for month
  • Die vollständige Regel für eine E-Mail-Adresse (RFC 5322) erlaubt an bestimmten Stellen Anführungszeichen, Kommentare und Leerzeichen. Ein Muster, das alles abdeckt, ist Hunderte Zeichen lang, und niemand pflegt es. Die echte Prüfung ist, eine E-Mail mit einem Bestätigungslink zu schicken.

Weitere Aufgaben

  • Passwort. Prüfe, ob das Passwort mindestens 8 Zeichen lang ist und mindestens eine Ziffer hat. Mach es mit zwei re.fullmatch: r".{8,}" und r".*\d.*".
  • Uhrzeit. Prüfe eine Uhrzeit in der Form hh:mm, von 00:00 bis 23:59.
  • Nur Ziffern. Behalte von "Tel.: +359 (88) 812-34-56" nur die Ziffern und das +, mit einem re.sub und [^...].

Test

Teste dich

1. Was prüft re.fullmatch(r"\d{4}", "2026")?
2. Was trifft . (ein Punkt) in einem Muster?
3. Welches Zeichen bedeutet „einmal oder öfter“?
4. Passt "München" in Python auf \w+?
5. Wie viele Teile ergibt re.split(r"[,;]\s*", "Brot, Milch;Kaffee")?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·