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 8

Zeit 5:50–6:40

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

CASE-008

Grep & Co. Datendetektei

Fallakte

Die Herausforderung des Backtrackers

Auftraggeber

Vor der Tür der Detektei (Absender: der Backtracker)

Beweismittel

Ein schwarzer Umschlag. Auf der Karte darin stehen ein einzeiliger Regex und eine Frage.

Zum Abschluss des Falls

Herausfinden, warum das Muster auf der Karte so langsam ist, und mit einem Muster antworten, das dieselben Strings findet, aber sofort fertig ist.

Am Morgen steckt ein schwarzer Umschlag im Türspalt der Detektei. Darin eine einzige Karte. Die Direktorin hält sie gegen das Licht und legt sie dann auf den Schreibtisch. „Er stellt sich also endlich vor. Mit einer Signatur statt mit einem Gesicht.“

^(a|aa)+$

이 패턴은 정답을 맞힌다. 그런데 왜 이렇게 느린가?
답은 패턴으로 보내라.
                                        (a+)+$

Die Karte ist auf Koreanisch geschrieben: „Dieses Muster liefert die richtige Antwort. Aber warum ist es so langsam? Antworte mit einem Muster.“

„Das kann man nur lösen, wenn man weiß, wie eine Regex-Engine denkt“, sagt die Direktorin. „Heute schauen wir der Engine in den Kopf.“

8-1. Die Engine probiert von vorn und geht zurück, wenn es hakt (10 Min.)

Eine Regex-Engine legt das Muster Stück für Stück von links an den String an. Passt etwas nicht, gibt sie nicht sofort auf, sondern nimmt die letzte Entscheidung zurück und probiert einen anderen Weg. Das nennt man Backtracking.

In Lektion 3 hieß es, Quantoren fressen standardmäßig „so viel wie möglich“ (sie sind gierig). Sehen wir uns an einem Beispiel an, was passiert, wenn man Tags aus HTML herausziehen will.

html = "<b>bold</b> and <i>it</i>"
re.findall(r"<.+>", html)   # ['<b>bold</b> and <i>it</i>']

Statt vier Tags kommt die ganze Zeile als ein einziger Treffer heraus. Verfolgen wir, was die Engine getan hat:

  1. < passt auf das erste <.
  2. .+ ist gierig und frisst alles bis zum Zeilenende.
  3. Jetzt muss das > im Muster passen, aber es ist kein Zeichen mehr übrig. Fehlschlag.
  4. .+ gibt die gefressenen Zeichen eins nach dem anderen zurück und versucht es erneut. Sobald es das allerletzte > zurückgegeben hat, klappt es.

Das Ergebnis reicht „vom äußersten < bis zum allerletzten >“. Falsch liegt nicht die Engine, sondern das Muster.

Beweislabor

re.search(r"", text)

Rückgabewert

<.+> fängt die ganze Zeile als einen Treffer. Ändere es so, dass nur ein einzelnes Tag gefangen wird. Die Fallbedingungen verlangen ein Muster, das auf ein Tag passt, aber nicht auf einen Brocken, der auch den Text zwischen den Tags mitfrisst.

8-2. Genügsame Quantoren: *? +? ?? {m,n}? (8 Min.)

Hängst du an einen Quantor ein ? an, wird er zu einem genügsamen (lazy) Quantor, der so wenig wie möglich frisst. Er nimmt zuerst nur das Minimum und frisst erst dann Zeichen für Zeichen mehr, wenn der Rest des Musters nicht passt.

gieriggenügsamprobiert zuerst
**?0-mal
++?1-mal
???0-mal
{m,n}{m,n}?m-mal
re.findall(r"<.+?>", html)          # ['<b>', '</b>', '<i>', '</i>']
re.findall(r"a+?", "aaa")           # ['a', 'a', 'a']
re.findall(r"a??", "a")             # ['', 'a', '']
re.search(r"\d{2,4}?", "12345").group()   # '12'

Genügsame Quantoren ändern, „wo die Engine anhält“. Deshalb sind sie praktisch, um Dinge mit einem klaren Endzeichen wie Tags kurz zu fangen.

8-3. Ein Problem, drei Antworten (4 Min.)

Hier die Möglichkeiten, Tags herauszuziehen, nebeneinander:

MusterErgebnisIn einem Satz
<.+>die ganze Zeile als ein Treffergierig, frisst zu viel
<.+?>vier Tagshält genügsam an
<[^>]+>vier Tagskann > von vornherein nicht fressen

<[^>]+> nutzt die negierte Klasse aus Lektion 2 und frisst nur „Zeichen, die kein > sind“. Zurückgehen muss es kaum, deshalb ist es am schnellsten und sagt auch am klarsten, was gemeint ist. Die Direktorin bringt es auf den Punkt: „Genauigkeit schlägt Genügsamkeit.“

Dots Tipp

Genügsame Quantoren sind keine Zauberei. Sie ändern nur, wo ein Treffer endet. Wie oft die Engine zurückgeht, wenn ein Treffer scheitert, bleibt genau gleich. Ein langsames Muster bleibt langsam, auch wenn du es genügsam machst.

8-4. Die Antwort auf die Herausforderung: zu viele Wege zurück (8 Min.)

Jetzt zum Muster auf der Karte, ^(a|aa)+$. Es findet „einen ganzen String aus einem oder mehreren a“. Es bedeutet also dasselbe wie ^a+$.

bool(re.fullmatch(r"^(a|aa)+$", "aaaa"))   # True
bool(re.fullmatch(r"^a+$", "aaaa"))        # True

Das Problem zeigt sich bei einem String, der nicht passt. Hängt am Ende ein b, wie in aaaa…ab, probiert die Engine jede Möglichkeit durch, die a in a und aa aufzuteilen, bevor sie den Fehlschlag zugibt. Und die Zahl dieser Aufteilungen explodiert, je mehr a es werden.

Gemessen auf dem Rechner der Detektei (Python 3.14):

Anzahl a^(a|aa)+$^a+$
160,25 msunter 0,04 ms
201,1 msunter 0,01 ms
247,2 msunter 0,01 ms
2851 msunter 0,01 ms

Mit jeweils 4 a mehr wird es etwa 7-mal langsamer. Bei gleichem Verhältnis wären es bei 40 a weit über 10 Sekunden. Solche Wiederholungen mit überlappenden Alternativen sind die typische Form, die das Backtracking explodieren lässt. Wiederholungen in Wiederholungen wie (a+)+ gehören zur selben Sorte.

Beweislabor

re.search(r"", text)

Rückgabewert

Setzt du das Muster von der Karte unverändert ein, bricht die 0,3-Sekunden-Sicherung des Labors die Ausführung ab. Ersetze es durch ein Muster, das dasselbe bedeutet, aber sofort fertig ist.

8-5. Quantoren, die nichts zurückgeben: *+ ++ ?+ {m,n}+ (6 Min.)

Seit Python 3.11 wird ein Quantor mit angehängtem + zu einem possessiven Quantor. Was er einmal gefressen hat, gibt er nicht mehr her, auch wenn der Rest scheitert. Damit verschwindet der Weg zurück ganz.

re.fullmatch(r"a*a", "aaa")    # <re.Match object; span=(0, 3), match='aaa'>
re.fullmatch(r"a*+a", "aaa")   # None

a*+a passt auf überhaupt keinen String. a*+ schnappt sich alle a und gibt keins zurück, also bleibt für das a dahinter nichts übrig. Genau diese Eigenschaft hilft aber, das Muster auf der Karte zu reparieren.

re.search(r"^(?:a|aa)++$", "a" * 28 + "b")   # None (sofort fertig)

++ macht die Wiederholung der Gruppe possessiv. Scheitert sie, probiert die Engine keine andere Aufteilung, sondern gibt sofort auf.

8-6. Gruppen, die als Ganzes nichts zurückgeben: (?>...) (4 Min.)

Die atomare Gruppe (atomic group) (?>...) gibt es ebenfalls seit 3.11. Hat der Inhalt der Gruppe einmal gepasst, wird das Ergebnis als Ganzes festgeschrieben, und die Engine kehrt nicht mehr in die Gruppe zurück. Possessive Quantoren sind eigentlich eine Kurzschreibweise für atomare Gruppen: a++ ist dasselbe wie (?>a+).

re.fullmatch(r"(?>a+)b", "aab")   # <re.Match object; span=(0, 3), match='aab'>
re.fullmatch(r"(?>a+)a", "aaa")   # None

Den Fall lösen (10 Min.)

Zusammen mit der Direktorin formulierst du die Antwort. Es gibt drei Wege, ein langsames Muster zu reparieren:

  1. Ohne Überlappung neu schreiben. Dafür sorgen, dass sich dieselben Zeichen nicht auf mehrere Arten aufteilen lassen. ^a+$ statt ^(a|aa)+$.
  2. Den Weg zurück abschneiden. Der possessive Quantor ^(?:a|aa)++$ oder die atomare Gruppe ^(?>(?:a|aa)+)$.
  3. Von außen absichern. Die Eingabelänge begrenzen und fremde Muster nur dort ausführen, wo es ein Zeitlimit gibt. Dass dieses Labor Muster in einem eigenen Prozess ausführt und nach 0,3 Sekunden abbricht, ist genau so eine Sicherung. (Das Drittanbieter-Paket regex hat ein timeout-Argument.)
import time
text = "a" * 40 + "b"
start = time.perf_counter()
print(re.search(r"^a+$", text))             # None
print(time.perf_counter() - start < 0.01)   # True

Du schreibst ^a+$ auf die Rückseite der Karte und steckst sie in den Umschlag. Die Direktorin nickt. „Kurz, lesbar, schnell. Eine gute Antwort.“

Fall abgeschlossen — Bestätigt, dass das Muster ^(a|aa)+$ aus der Herausforderung wegen überlappender Alternativen bei einem Fehlschlag exponentiell länger braucht. Mit dem gleichbedeutenden ^a+$ geantwortet. Die Zeitsicherung des Labors bleibt bestehen.

Übungen

  1. Was liefert re.findall(r'".+"', 'say "hi" and "yo"')? Rate zuerst, dann repariere es auf zwei Arten (genügsamer Quantor, negierte Klasse), sodass jedes Anführungszeichenpaar einzeln gefangen wird.
  2. Erkläre, warum (\d+)+$ eine gefährliche Form ist, und schreib ein sicheres Muster mit derselben Bedeutung.
  3. Was kommt jeweils heraus, wenn du a{2,4}? und a{2,4}+ mit findall auf "aaaaa" loslässt? Prüf es im Labor.

Die Spur des Backtrackers

(a+)+$

Antwort erhalten. Die nächste Aufgabe ist der Tresor eurer Detektei. Drei Regeln für das Passwort, Reihenfolge egal. Vorausgesetzt, ihr könnt alle an derselben Stelle prüfen.

Das Notizbuch der Direktorin

  • Die Engine probiert von links und nimmt die letzte Entscheidung zurück, wenn es hakt (Backtracking).

  • * + ? {m,n} sind gierig. Mit angehängtem ? werden sie genügsam: *? +? ?? {m,n}?

  • Bei klarem Endzeichen wie bei Tags ist eine negierte Klasse wie <[^>]+> am klarsten und schnellsten.

  • Überlappende Wiederholungen wie (a|aa)+ oder (a+)+ explodieren bei einem Fehlschlag. Genügsam machen hilft nicht.

  • Possessive Quantoren *+ ++ ?+ {m,n}+ und atomare Gruppen (?>...) geben nichts zurück (3.11+). a++ ist (?>a+).

  • Fremde Muster: Eingabelänge begrenzen und nur mit Zeitlimit ausführen.

← Zurück

Lektion 7 · Die Nacht vor der Story: Ersetzen und Aufteilen

Weiter →

Lektion 9 · Die Regeln des Tresors: Lookaround

Ü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