R

ReflexKR

Reflex-Dokumentation

Reguläre Ausdrücke zum Ansehen

·

Kursüberblick

1

Die verschwundene Zahlung: Text wörtlich finden

2

Der Fluchtwagen: Zeichenklassen

3

Das zerrissene Telefonbuch: Quantoren

4

Die falschen Anmeldungen: Anker und Validierung

5

Daten in jedem Format: Gruppen und Alternativen

6

Der Zugriff um 3 Uhr nachts: benannte Gruppen und Match-Objekte

7

Die Nacht vor der Story: Ersetzen und Aufteilen

8

Die Herausforderung des Backtrackers: gierig, genügsam, possessiv

9

Die Regeln des Tresors: Lookaround

10

Gleiche Masche: Rückverweise und Bedingungen

11

Der unlesbare Regex: Flags und Kompilieren

12

Die Enttarnung des Backtrackers: Rückblick und ein Tokenizer

Reguläre Ausdrücke zum Ansehen
Lektion 4

Zeit 2:30–3:20

Die falschen Anmeldungen: Anker und Validierung

CASE-004

Grep & Co. Datendetektei

Fallakte

Die falschen Anmeldungen

Auftraggeber

Frau Jeong, Betreiberin des Onlineshops „Markt Jeong“

Beweismittel

Die Liste der Registrierungsanfragen und eine Python-Funktion, die die Regeln für Benutzernamen prüft.

Zum Abschluss des Falls

Fische alle Benutzernamen heraus, die gegen die Regeln verstoßen, und finde heraus, warum die Prüfung versagt hat.

Gegen Mittag kommt Frau Jeong herein, ihren Laptop fest an sich gedrückt. „Ich habe ganz klare Regeln für Benutzernamen festgelegt. Und trotzdem kommen hier die seltsamsten Namen rein.“ Die Chefin schaut lange auf den Bildschirm, dann sagt sie knapp: „Die Regel stimmt. Nur die Frage ist falsch gestellt.“

So sehen die Namensregel von Markt Jeong und die Prüffunktion aus. Die Regel steht auf Koreanisch in der Doku des Shops:

아이디 규칙: 영문 소문자로 시작하고, 소문자·숫자·밑줄(_)만 써서 4~16자

Auf Deutsch: Benutzername: beginnt mit einem lateinischen Kleinbuchstaben, besteht nur aus Kleinbuchstaben, Ziffern und Unterstrichen (_), insgesamt 4 bis 16 Zeichen.

import re

ID = r"[a-z][a-z0-9_]{3,15}"

def is_valid_id(uid):
    return re.search(ID, uid) is not None

Und hier die Registrierungsanfragen:

2026-10-18 14:02:51 signup id=jeong_fan
2026-10-19 03:11:02 signup id=ok_id!!!
2026-10-19 03:11:04 signup id=_admin
2026-10-19 03:11:05 signup id=ABC123
2026-10-19 03:11:07 signup id=a1
2026-10-19 09:40:13 signup id=minsu99

4-1. Den Vorfall nachstellen: search findet überall (5 Min.)

Sieh dir zuerst an, was Frau Jeongs Prüfung durchlässt.

print(is_valid_id("ok_id!!!"))   # True   ← trotz Ausrufezeichen durchgekommen
print(is_valid_id("_admin"))     # True   ← beginnt mit Unterstrich, trotzdem durch
print(is_valid_id("ABC123"))     # False

re.search fragt: „Gibt es irgendwo in diesem String einen Teil, der zur Regel passt?“ In ok_id!!! steckt ok_id, in _admin steckt admin, also Erfolg.

re.search(ID, "ok_id!!!")    # <re.Match object; span=(0, 5), match='ok_id'>
re.search(ID, "!!!ok_id")    # <re.Match object; span=(3, 8), match='ok_id'>

Eine Eingabeprüfung muss eine andere Frage stellen: „Passt der ganze String zur Regel?“ Dafür muss im Muster stehen: „Hier ist der Anfang, hier ist das Ende.“

4-2. ^ und $: am Anfang und am Ende festnageln (8 Min.)

Außerhalb eckiger Klammern steht ^ für den Anfang des Strings und $ für das Ende des Strings. Solche Zeichen heißen Anker (anchor).

re.search(r"^id", "id=kim")       # <re.Match object; span=(0, 2), match='id'>
re.search(r"^id", "my id=kim")    # None
re.search(r"kim$", "id=kim")      # <re.Match object; span=(3, 6), match='kim'>
re.search(r"kim$", "id=kim!")     # None
  • Anker belegen kein Zeichen. Sie passen auf eine Position zwischen zwei Zeichen. Deshalb besteht der Treffer von ^id weiterhin aus den zwei Zeichen id.
  • Verwechsle das nicht mit [^...] aus Lektion 2. Ganz vorn in eckigen Klammern verneint ^, außerhalb davon steht es für den Anfang.

Setzt du ^ und $ vor und hinter das Muster der Prüfung, muss der passende Teil vom ersten bis zum letzten Zeichen reichen.

Beweislabor

re.search(r"", text)

Rückgabewert

Die Fallbedingungen werden mit search geprüft. Setz ein ^ vor und ein $ hinter das Muster und halte die falschen Namen draußen.

4-3. match und fullmatch (8 Min.)

Auch ohne selbst Anker zu schreiben, kannst du über die Funktion wählen, wo verglichen wird.

FunktionFragtMit Ankern geschrieben
re.searchPasst irgendwo ein Teil?(keine Anker)
re.matchPasst ein Teil ab dem Anfang?\AMuster
re.fullmatchPasst das Ganze?\AMuster\Z

\A und \Z sind Anker ähnlich wie ^ und $. Den Unterschied siehst du in 4-4.

re.match(ID, "ok_id!!!")       # <re.Match object; span=(0, 5), match='ok_id'>
re.match(ID, "!!!ok_id")       # None
re.fullmatch(ID, "ok_id!!!")   # None
re.fullmatch(ID, "ok_id")      # <re.Match object; span=(0, 5), match='ok_id'>
  • Anders als der Name vermuten lässt, prüft re.match nicht, ob alles passt. Es reicht, wenn der Anfang passt, und so lässt es ok_id!!! durch.
  • Die richtige Wahl für Eingabeprüfungen ist re.fullmatch. Es ist sicher, auch wenn du die Anker vergisst, und es umgeht die Falle aus dem nächsten Abschnitt.

Beweislabor

re.search(r"", text)

Rückgabewert

Klick nacheinander auf search, match und fullmatch. Ändere den Text in !!!ok_id und ok_id und vergleiche die drei Funktionen.

4-4. Die Falle bei $ – und \A, \Z, \z (6 Min.)

$ hat eine versteckte Regel. Es passt nicht nur am echten Ende des Strings, sondern auch direkt vor einem abschließenden Zeilenumbruch. Deshalb lässt ^abc$ auch "abc\n" mit angehängtem Zeilenumbruch durch.

re.search(r"^abc$", "abc\n")       # <re.Match object; span=(0, 3), match='abc'>
re.fullmatch(r"abc", "abc\n")      # None
re.search(r"\Aabc\Z", "abc\n")     # None
  • \A passt nur ganz am Anfang des Strings, \Z nur ganz am Ende. Eine Ausnahme für Zeilenumbrüche gibt es nicht.
  • Seit Python 3.14 gibt es außerdem \z. In Python ist es genau dasselbe wie \Z. Es kam dazu, damit die Schreibweise zu Regexen in anderen Sprachen passt.
re.search(r"\Aabc\z", "abc")      # <re.Match object; span=(0, 3), match='abc'>
re.search(r"\Aabc\z", "abc\n")    # None

Dots Tipp

Zeilen, die du aus einer Datei liest, haben oft noch einen Zeilenumbruch am Ende. Prüfst du mit ^...$, tut das Muster so, als hätte es ihn nicht gesehen, und lässt ihn durch. Dann landet der Benutzername samt Zeilenumbruch in der Datenbank. Zum Prüfen also fullmatch oder \A...\Z.

4-5. Wortgrenzen: \b und \B (8 Min.)

Da ist noch die Hausaufgabe aus Lektion 3: Das Telefonnummer-Muster hat mitten in 1234-5678-9012 mit 234 angefangen. Diesmal brauchst du keinen Anker für den ganzen String, sondern einen, der in einem langen Text Anfang und Ende einer einzelnen Nummer markiert.

\b ist eine Wortgrenze. Sie passt auf eine Stelle zwischen einem \w (Buchstabe, Ziffer, Unterstrich) und einem \W (jedes andere Zeichen) oder auf Anfang oder Ende des Strings, wenn dort ein \w steht. \B ist das Gegenteil, eine Stelle, die keine Grenze ist. Beide belegen kein Zeichen.

re.findall(r"\bid\b", "id=kim userid=lee id_card")   # ['id']
re.findall(r"\Bid\b", "id=kim userid=lee id_card")   # ['id']
re.findall(r"\bid\B", "id=kim userid=lee id_card")   # ['id']

Alle drei Ergebnisse sind ['id'], aber gefunden wurden sie an verschiedenen Stellen.

  • \bid\b : das id in id=kim, mit einer Grenze davor und dahinter
  • \Bid\b : das id in userid, davor keine Grenze
  • \bid\B : das id in id_card, dahinter ein Unterstrich (\w), also keine Grenze

Setzt du \b vor und hinter das Telefonnummer-Muster, kann es nicht mehr mitten in einer Zahl anfangen. Zwischen 1 und 2 ist keine Grenze, denn beide sind \w.

re.findall(r"\b\d{2,3}[-. ]?\d{3,4}[-. ]?\d{4}\b", phonebook)
# ['010-0000-1234', '010 0000 9876', '02-0000-5678', '010.0000.4321']

Eine Sache noch: In Python ist auch Hangul \w. Direkt vor Ziffern, die unmittelbar an Hangul hängen, gibt es also keine Grenze. Im folgenden Beispiel steht 전화 für „Telefon“.

re.findall(r"\b\d{2,3}-\d{3,4}-\d{4}\b", "전화010-0000-1234")    # []
re.findall(r"\b\d{2,3}-\d{3,4}-\d{4}\b", "전화 010-0000-1234")   # ['010-0000-1234']

Beweislabor

re.search(r"", text)

Rückgabewert

Das Telefonnummer-Muster aus Lektion 3. Setz \b davor und dahinter, damit Kartennummer und zu lange Nummern draußen bleiben.

4-6. Zeile für Zeile: ein erster Blick auf re.MULTILINE (5 Min.)

Stehen wie bei den Registrierungen mehrere Zeilen in einem String, passt ^ nur am Anfang der ersten Zeile. Willst du das Datum am Anfang jeder Zeile herausziehen, gibst du das Flag re.MULTILINE (kurz re.M) mit. Dann passen ^ und $ am Anfang und Ende jeder Zeile.

re.findall(r"^\d{4}-\d\d-\d\d", signups)
# ['2026-10-18']
re.findall(r"^\d{4}-\d\d-\d\d", signups, re.MULTILINE)
# ['2026-10-18', '2026-10-19', '2026-10-19', '2026-10-19', '2026-10-19', '2026-10-19']

Auch mit re.M passen \A und \Z weiterhin nur am Anfang und Ende des ganzen Strings.

re.findall(r"^\d", "1\n2", re.M)    # ['1', '2']
re.findall(r"\A\d", "1\n2", re.M)   # ['1']

Alle Flags behandeln wir ausführlich in Lektion 11. Für heute reicht: Es gibt einen Schalter, der ^ und $ auf einzelne Zeilen umstellt.

Beweislabor

re.search(r"", text)

Flags

Rückgabewert

Schalte den Knopf re.M ein und aus. Eingeschaltet passt ^ an jedem Zeilenanfang. Was passiert, wenn du ^ durch \A ersetzt?

Den Fall lösen (10 Min.)

Ersetz in der Prüfung re.search durch re.fullmatch. Das Muster bleibt, wie es ist.

def is_valid_id(uid):
    return re.fullmatch(r"[a-z][a-z0-9_]{3,15}", uid) is not None

for line in signups.splitlines():
    uid = line.split("id=")[1]
    if not is_valid_id(uid):
        print(line)
# 2026-10-19 03:11:02 signup id=ok_id!!!
# 2026-10-19 03:11:04 signup id=_admin
# 2026-10-19 03:11:05 signup id=ABC123
# 2026-10-19 03:11:07 signup id=a1

Alle vier aussortierten Anfragen verstoßen gegen die Regeln: ok_id!!! hat Ausrufezeichen, _admin beginnt mit einem Unterstrich, ABC123 hat Großbuchstaben, a1 ist zu kurz.

Fall abgeschlossen — Die Prüfung der Benutzernamen hat mit re.search alles durchgelassen, was „wenigstens teilweise passt“. Nach der Umstellung auf re.fullmatch wurden 4 regelwidrige Anmeldungen blockiert.

Doch die Chefin fährt mit dem Finger die Uhrzeiten der vier Zeilen entlang. 03:11:02, 03:11:04, 03:11:05, 03:11:07. „Viermal in fünf Sekunden. Um drei Uhr nachts. Das war keine Menschenhand.“ Frau Jeongs Gesicht wird starr. Die falschen Konten kamen alle auf einmal, in derselben Nacht. Wer sie geschickt hat, weiß noch niemand.

Damit endet Teil 1. Ein Zeichen, eine Stelle, Anzahlen, Grenzen: Du weißt jetzt, wie man aufschreibt, was man sucht. In Teil 2 lernst du, aus dem Gefundenen die Teile herauszulösen, die du brauchst, und jagst damit den Besucher aus dieser Nacht.

Übungen

  1. Überleg dir, was re.match(r"\d{4}", "2026-10-19") und re.match(r"\d{4}", "#2026") ergeben, und probier es aus.
  2. Wie würdest du das Muster ändern, wenn zur Namensregel „darf nicht mit einem Unterstrich enden“ dazukommt? (Tipp: Schreib das letzte Zeichen extra.)
  3. Welches cat finden jeweils re.findall(r"\bcat\b", "cat concat cats") und re.findall(r"\Bcat", "cat concat cats")?

Das Notizbuch der Direktorin

  • ^ ist der Anfang, $ das Ende. Anker passen nicht auf Zeichen, sondern auf Positionen.

  • re.search irgendwo, re.match nur am Anfang, re.fullmatch das Ganze. Eingabeprüfung: fullmatch.

  • $ passt auch vor einem abschließenden Zeilenumbruch. Das Ende ohne diese Ausnahme: \Z (seit 3.14 auch \z).

  • \b ist die Grenze zwischen \w und \W, \B eine Stelle ohne Grenze. Auch Hangul ist \w.

  • Mit re.M passen ^ $ an jeder Zeile. \A \Z beziehen sich weiter auf den ganzen String.

← Zurück

Lektion 3 · Das zerrissene Telefonbuch: Quantoren

Weiter →

Lektion 5 · Daten in jedem Format: Gruppen und Alternativen

Über neue Lektionen informiert werden

Wir schicken dir eine E-Mail, wenn dieser Kurs neue Lektionen bekommt oder ein Folgekurs erscheint.

Erhoben: deine E-Mail-Adresse · Zweck: Hinweise auf neue Lektionen oder den Start dieses Kurses · Speicherdauer: bis du die Löschung verlangst. Keine andere Nutzung, keine Weitergabe. Antworte einfach auf eine Benachrichtigung, dann löschen wir sie sofort.

Fragen & Antworten

Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.

Wird als reiner Text angezeigt, ohne Links oder Formatierung.

R

ReflexKR

Python und Reflex zum Anfassen. Alle Beispiele wurden mit Reflex 0.10.0 ausgeführt und geprüft.

ReflexKR ist kostenlos und ohne Werbebanner. Wenn es dir geholfen hat, kannst du es mit einem Kaffee unterstützen. ☕ Spendier uns einen Kaffee

Hinweis zu Affiliate-Links
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex