regex in Python#
Erklär-Notebook zu 14: String Manipulation and Regular Expressions > flexible-pattern-matching-with-regular-expressions . Das Ausprobier-Notebook aus der Vorlesung 2023-12-06 wurde verschoben nach regex.
HowTo, empfehlenswert: https://docs.python.org/3/howto/regex.html
zum Nachschlagen sollte man auch die Original-Doku kennen: https://docs.python.org/3/library/re.html; dort gibt es auch einige fortgeschrittene Beispiele: https://docs.python.org/3/library/re.html#regular-expression-examples
Vorbemerkung, Achtung, nicht verwechseln:
In der
bash
bedeutet der Stern: Null oder mehr Zeichen
!ls *AUTO*.ipynb
e_r1_quizz_AUTO.ipynb f_f7_AUTO.ipynb
e_r2b_AUTO.ipynb f_f8_2024-03-03_AUTO.ipynb
e_r2_funktionsparameter_BMI_AUTO.ipynb json-beispiel42_AUTO.ipynb
f_f1_AUTO.ipynb q_r2b_AUTO.ipynb
f_f2_AUTO.ipynb quizz_mc_typen_AUTO.ipynb
f_f3_bibliotheken-kernfunktionen_AUTO.ipynb zweimalzwei_AUTO.ipynb
f_f4_AUTO.ipynb zweiundvierzig_AUTO.ipynb
f_f5_AUTO.ipynb
in RegEx bedeutet der Stern:
null mal oder öfters das vorausgehende Teilpattern
Bsp: ISO 8601 Datum suchen#
gegeben: ein Text mit Datumsangaben, z.B.
text = ("""Der 24.12.2023 ist in diesem Jahr ein Sonntag.
Der Unterricht geht bis Freitag 2023-12-22, weiter dann 2024.01.08.
In 2024 gibt es sogar einen Donnerstag 2024-02-29, aber nicht 2024-04-31.
Auch klar: Amerikaner notieren den Anschlag nine-eleven 9/11 mit 09/11/2001;
Europäer notieren den 11. September 2001 mit 11-09-2001.
und das gibt es nicht: 2024-13-98""")
Gesucht:
Identifiziere und markiere alle Datumsangaben, die korrekt im ISO-Format ISO_8601 angegeben sind,
und übersetze diese – und nur diese – auf DE, mit deutschen Monatsnamen.
Eine allgemeine regex für alle erlaubten Datumsangaben nach ISO 8601 ist schwierig, siehe z.B. https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html.
Wir beschränken uns hier zunächst auf Angebaben der Art
YYYY-MM-TT
, mit-
als einzigem erlaubten Trennzeichen.
Hier geht es darum, wie man eine Regex in Python verwendet, die man schon konstruiert hat. Die Regex selbst wird man nicht hier im Quellcode konstruieren (geht nur für Experten) und testen (geht auch für Experten nicht), sondern mit einem interaktiven Frontend, wie z.B. mit https://regex101.com/.
In unserem Fall begnügen wir uns mit einem sehr einfachen Pattern, nämlich (\d{4})-(\d\d)-(\d\d)
import re
# regex ohne Gruppierungen
datum_regex_string = r"\d{4}-\d\d-\d\d"
# interessanter: regex mit Gruppierungen
datum_regex_string2 = r"(\d{4})-(\d\d)-(\d\d)"
datum_regex = re.compile(datum_regex_string2)
datum_regex
re.compile(r'(\d{4})-(\d\d)-(\d\d)', re.UNICODE)
type(datum_regex)
re.Pattern
re.search()#
Nachlesen: https://docs.python.org/3/howto/regex.html#performing-matches
search()
liefert uns den ersten Treffer unseres Musters im Text. Das Ergebnis ist ein Objekt vom Typ re.Match
:
datum_regex_match = datum_regex.search(text)
datum_regex_match
<re.Match object; span=(80, 90), match='2023-12-22'>
type(datum_regex_match)
re.Match
So ein re.Match
-Objekt hat verschiedene Methoden, z.B. start()
, end()
, group()
:
datum_regex_match.start(), datum_regex_match.end(), datum_regex_match.group()
(80, 90, '2023-12-22')
text[datum_regex_match.start() : datum_regex_match.end() ]
'2023-12-22'
datum_regex_match.group()
'2023-12-22'
datum_regex_match.group(1)
'2023'
datum_regex_match.groups()
('2023', '12', '22')
re.findall()#
Wenn wir nur an einer Liste aller Datumsangaben – ggf. auch gleich nach Subgroups aufgeteilt – interessiert sind, können wir findall()
benutzen:
datum_regex_findall = datum_regex.findall(text)
datum_regex_findall
[('2023', '12', '22'),
('2024', '02', '29'),
('2024', '04', '31'),
('2024', '13', '98')]
re.finditer()#
Wenn wir nicht nur an (hier: einer Liste von) allen Strings, sondern an (hier: einer Sequenz) von allen Match-Objekten interessiert sind, erzeugen wir einen Iterator:
datum_regex_iterator = datum_regex.finditer(text)
datum_regex_iterator
<callable_iterator at 0x70c548714c40>
for Treffer in datum_regex_iterator:
print(Treffer)
<re.Match object; span=(80, 90), match='2023-12-22'>
<re.Match object; span=(156, 166), match='2024-02-29'>
<re.Match object; span=(179, 189), match='2024-04-31'>
<re.Match object; span=(350, 360), match='2024-13-98'>
Zur Vollständigkeit, siehe 10: Iterators: Wenn man durch einen Iterator durchgelaufen ist, ist er “verbraucht”. Wenn an ihn nochmal benutzt, liefert er keine Ergebnisse mehr:
for Treffer in datum_regex_iterator:
print(Treffer)
Wenn man tatsächlich eine persistente Liste von Matchobjekten benötigt, kann sich ja leicht eine solche bauen:
datum_regex_matchlist = [ Treffer for Treffer in datum_regex.finditer(text) ]
datum_regex_matchlist
[<re.Match object; span=(80, 90), match='2023-12-22'>,
<re.Match object; span=(156, 166), match='2024-02-29'>,
<re.Match object; span=(179, 189), match='2024-04-31'>,
<re.Match object; span=(350, 360), match='2024-13-98'>]
Eine solche Liste von Matches kann man nun z.B. in einer Schleife durchgehen, um mit den Match-Objekten etwas sinnvolles zu tun:
def iso_8601_nach_deutsch(Jahr, Monat, Tag):
"""empfängt ein Datum als 3 Strings 'JJJJ', 'MM', 'TT';
gibt das Datum mit expamdierten Monatsnamen in DE zurück."""
Monate = { 1: "Januar", 2: "Februar", 3: "März", 4: "April",
5: "Mai", 6: "Juni", 7: "Juli", 8: "August",
9: "September", 10: "Oktober", 11: "November", 12: "Dezember" }
return f"{Tag}. {Monate.get(int(Monat), 'UNGÜLTIG')} {Jahr}"
iso_8601_nach_deutsch("2023", "13", "24")
'24. UNGÜLTIG 2023'
for Treffer in datum_regex_matchlist:
# Treffer ist ein Match-Objekt
# print(f"{Treffer=}")
j, m, t = Treffer.group(1), Treffer.group(2), Treffer.group(3)
print(iso_8601_nach_deutsch(j, m, t))
22. Dezember 2023
29. Februar 2024
31. April 2024
98. UNGÜLTIG 2024
re.sub()#
re.sub()
ist die Funktion Suchen und ersetzen. Damit erfüllen wir die ursprüngliche Aufgabe:
Markiere alle Datumsangaben im korrekten ISO 8601-Format
In der Funktion sub()
können wir mit \1
, \2
etc. auf unsere Subgroups zugreifen und diese im Ersetzungstext wiederverwenden.
print(datum_regex.sub(r"<\3.\2.\1>", text))
Der 24.12.2023 ist in diesem Jahr ein Sonntag.
Der Unterricht geht bis Freitag <22.12.2023>, weiter dann 2024.01.08.
In 2024 gibt es sogar einen Donnerstag <29.02.2024>, aber nicht <31.04.2024>.
Auch klar: Amerikaner notieren den Anschlag nine-eleven 9/11 mit 09/11/2001;
Europäer notieren den 11. September 2001 mit 11-09-2001.
und das gibt es nicht: <98.13.2024>