Python Strings Gruppe — Regex Erfassungsgruppen
Python Regex Erfassungsgruppen verstehen: group(), groups(), benannte Gruppen, nicht erfassende Gruppen und Rückverweise mit klaren Beispielen.
Wenn Pythons re-Modul eine Übereinstimmung in einem string findet, gibt es ein Match-Objekt zurück. Das Match-Objekt ist mehr als ein einfaches Ja/Nein-Ergebnis — es merkt sich jedes Teilmuster, das Sie in Klammern eingeschlossen haben, sogenannte Erfassungsgruppen. Die Methoden .group() und .groups() werden verwendet, um diese erfassten Teile abzurufen.
Dieses Kapitel behandelt alles, was Sie brauchen, um Gruppen sicher zu verwenden: nummerierte Gruppen, benannte Gruppen, nicht erfassende Gruppen, Rückverweise in Ersetzungen und häufige Fallstricke.
Was ist eine Erfassungsgruppe?
Eine Erfassungsgruppe ist ein Teil eines Regex-Musters, der in einfache Klammern (...) eingeschlossen ist. Wenn das Muster übereinstimmt, speichert Python den Text, der zu jedem Klammerpaar passt, separat — zusätzlich zur Speicherung der gesamten Übereinstimmung.
import re
m = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-15')
print(m.group(0)) # 2024-03-15 — the whole match
print(m.group(1)) # 2024 — first group
print(m.group(2)) # 03 — second group
print(m.group(3)) # 15 — third groupGruppen werden von links nach rechts nummeriert, beginnend bei 1, in der Reihenfolge, in der ihre öffnende Klammer erscheint. group(0) (oder einfach group() ohne Argument) gibt immer die gesamte Übereinstimmung zurück.
Die .group()-Methode
.group(n) gibt den Text zurück, der von der n-ten Erfassungsgruppe erfasst wurde. Sie können mehrere Indizes auf einmal übergeben, um ein Tupel von Ergebnissen zu erhalten.
import re
m = re.search(r'(\w+)@(\w+)\.(\w+)', '[email protected]')
# Individual groups
print(m.group(1)) # alice
print(m.group(2)) # example
print(m.group(3)) # com
# Multiple at once
print(m.group(1, 3)) # ('alice', 'com')Wenn eine Gruppe im Muster existiert, aber nicht an der Übereinstimmung teilgenommen hat (beispielsweise weil sie in einem optionalen Abschnitt lag), gibt group(n) None zurück.
import re
# group(1) is optional — it may not match
m = re.search(r'(\d+)?-(\d+)', 'abc-456')
print(m.group(1)) # None (the optional \d+ did not match)
print(m.group(2)) # 456Die .groups()-Methode
.groups() gibt ein Tupel zurück, das den von jeder Erfassungsgruppe erfassten Text in Reihenfolge enthält. Es ist eine praktische Abkürzung, wenn Sie alle Gruppen auf einmal möchten.
import re
m = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-15')
print(m.groups()) # ('2024', '03', '15')Standardwert für nicht übereinstimmende Gruppen angeben
Wenn eine Gruppe nicht an der Übereinstimmung teilgenommen hat, erscheint sie als None im Tupel. Sie können ein default-Argument angeben, um diese None-Werte durch etwas Nützlicheres zu ersetzen.
import re
m = re.search(r'(\d+)?-(\d+)', 'abc-456')
print(m.groups()) # (None, '456')
print(m.groups(default='0')) # ('0', '456')Benannte Gruppen mit (?P<name>...)
Nummerierte Gruppen werden bei komplexen Mustern schwer zu verfolgen. Benannte Gruppen ermöglichen es Ihnen, jeder Gruppe mit der Syntax (?P<name>...) ein aussagekräftiges Label zuzuweisen und den Wert dann nach Name statt nach Position abzurufen.
import re
pattern = r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'
m = re.search(pattern, '2024-03-15')
print(m.group('year')) # 2024
print(m.group('month')) # 03
print(m.group('day')) # 15Benannte Gruppen haben weiterhin eine Nummer, sodass Sie auf sie auf beiden Wegen zugreifen können.
Die .groupdict()-Methode
Wenn Sie benannte Gruppen haben, gibt .groupdict() ein Dictionary zurück, das jeden Namen seinem erfassten Text zuordnet. Dies ist nützlich, wenn Sie eine Übereinstimmung in ein strukturiertes Objekt entpacken möchten.
import re
m = re.search(
r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
'2024-03-15'
)
print(m.groupdict())
# {'year': '2024', 'month': '03', 'day': '15'}Das Ergebnis können Sie direkt verwenden:
date_parts = m.groupdict()
print(f"{date_parts['day']}/{date_parts['month']}/{date_parts['year']}")
# 15/03/2024Nicht erfassende Gruppen mit (?:...)
Manchmal müssen Sie einen Teil eines Musters für die Anwendung eines Quantors oder einer Alternation gruppieren, möchten aber nicht, dass diese Gruppe in den Übereinstimmungsergebnissen erscheint. Verwenden Sie (?:...) — eine nicht erfassende Gruppe.
import re
# Without non-capturing group: both parts appear in groups()
m1 = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-15')
print(m1.groups()) # ('2024', '03', '15')
# Year grouped but not captured — month and day are groups 1 and 2
m2 = re.search(r'(?:\d{4})-(\d{2})-(\d{2})', '2024-03-15')
print(m2.groups()) # ('03', '15')Nicht erfassende Gruppen sind eine bewährte Praxis, wenn Sie eine bestimmte Teilübereinstimmung nicht benötigen — sie halten Ihre Gruppenindizes sauber und vermeiden unnötigen Speicherverbrauch.
Verschachtelte Gruppen
Gruppen können verschachtelt werden. Die Nummerierung basiert immer auf der Position der öffnenden Klammer, von links nach rechts gezählt.
import re
# ((\d{4})-(\d{2}))-(\d{2})
# group 1 = the outer (year-month pair)
# group 2 = year
# group 3 = month
# group 4 = day
m = re.search(r'((\d{4})-(\d{2}))-(\d{2})', '2024-03-15')
print(m.group(1)) # 2024-03
print(m.group(2)) # 2024
print(m.group(3)) # 03
print(m.group(4)) # 15
print(m.groups()) # ('2024-03', '2024', '03', '15')Gruppen mit re.findall()
Wenn Sie re.findall() verwenden und das Muster genau eine Erfassungsgruppe enthält, gibt es eine Liste von strings zurück — einen pro Übereinstimmung.
Wenn das Muster zwei oder mehr Erfassungsgruppen enthält, gibt es eine Liste von Tupeln zurück.
import re
# One group → list of strings
emails = '[email protected], [email protected]'
usernames = re.findall(r'(\w+)@\w+\.\w+', emails)
print(usernames) # ['alice', 'bob']
# Two groups → list of tuples
pairs = re.findall(r'(\w+)@(\w+)\.com', emails)
print(pairs) # [('alice', 'gmail'), ('bob', 'yahoo')]Gruppen mit re.finditer()
re.finditer() gibt einen Iterator von Match-Objekten zurück, sodass Sie auf jedem einzeln .group() und .groups() aufrufen können. Dies ist der flexibelste Ansatz, wenn Sie für jedes Vorkommen vollständige Match-Details benötigen.
import re
text = '[email protected], [email protected]'
for m in re.finditer(r'(?P<user>\w+)@(?P<domain>\w+)\.com', text):
print(m.group('user'), '->', m.group('domain'))
# alice -> gmail
# bob -> yahooRückverweise in re.sub()
Erfassungsgruppen ermöglichen auch Rückverweise in re.sub(). Im Ersetzungsstring beziehen sich \1, \2, … auf den von Gruppe 1, Gruppe 2 usw. erfassten Text. Benannte Gruppen verwenden \g<name>.
import re
# Swap first and last name
result = re.sub(r'(\w+)\s(\w+)', r'\2 \1', 'John Smith')
print(result) # Smith John
# Same using named groups
result2 = re.sub(
r'(?P<first>\w+)\s(?P<last>\w+)',
r'\g<last> \g<first>',
'Jane Doe'
)
print(result2) # Doe JanePosition einer Gruppe: .start(), .end(), .span()
Match-Objekte geben die Position jeder Gruppe preis, nicht nur ihren Text. Übergeben Sie eine Gruppennummer (oder einen Namen) an .start(), .end() oder .span().
import re
m = re.search(r'(\d{4})-(\d{2})', '2024-03')
print(m.span(1)) # (0, 4) — year occupies indices 0..3
print(m.start(2)) # 5 — month starts at index 5
print(m.end(2)) # 7 — month ends before index 7Kurzübersicht
| Methode | Gibt zurück | Hinweise |
|---|---|---|
m.group() oder m.group(0) | Gesamter Übereinstimmungsstring | Immer verfügbar |
m.group(n) | Text, der von Gruppe n erfasst wurde | None, wenn Gruppe nicht übereinstimmte |
m.group(n, m, ...) | Tupel von Gruppen | Mehrere Indizes in einem Aufruf |
m.groups() | Tupel aller Gruppen | Optionales default= für nicht übereinstimmende |
m.groupdict() | Dict benannter Gruppen | Nur benannte Gruppen erscheinen |
m.start(n) / m.end(n) | Start-/Endindex von Gruppe n | 0 übergeben für gesamte Übereinstimmung |
m.span(n) | (start, end)-Tupel | Kurzform für beides |
Häufige Fehler
Verwenden von .group() ohne Überprüfung auf None. re.search() gibt None zurück, wenn keine Übereinstimmung gefunden wurde. Das direkte Aufrufen von .group() auf dem Ergebnis löst daher einen AttributeError aus. Schützen Sie den Aufruf immer:
import re
m = re.search(r'(\d+)', 'no digits here')
if m:
print(m.group(1))
else:
print('no match')
# no digits hereVerwechslung von .group() und .groups(). .group(1) gibt einen string zurück; .groups() gibt immer ein Tupel zurück. Das Vermischen beider führt zu schwer erkennbaren Typfehlern.
Vergessen, dass re.findall() mit Gruppen seine Form ändert. Ohne Gruppen gibt es eine flache Liste von strings zurück; mit Gruppen gibt es eine Liste von Tupeln zurück. Das Hinzufügen oder Entfernen einer Erfassungsgruppe kann dazu führen, dass Code, der das Ergebnis verarbeitet, still versagt.
Wann welcher Ansatz zu verwenden ist
- Verwenden Sie nummerierte Gruppen für einfache, kurze Muster mit wenigen Gruppen.
- Verwenden Sie benannte Gruppen, wenn Muster komplex sind oder wenn Sie das Ergebnis über mehrere Codezeilen verwenden — die Namen dienen als Selbstdokumentation.
- Verwenden Sie nicht erfassende Gruppen
(?:...), wann immer Sie eine Gruppierung nur zum Anwenden eines Quantors oder einer Alternation benötigen, nicht zur Extraktion eines Wertes. - Verwenden Sie
.groups(), um alle Erfassungen auf einmal in ein Tupel zu entpacken. - Verwenden Sie
.groupdict(), wenn alle Gruppen benannt sind und Sie ein Dictionary möchten.
Einen umfassenderen Überblick über das re-Modul und seine Mustersyntax finden Sie im Kapitel Python RegEx. Für string-Operationen, die kein Regex erfordern, siehe Strings modifizieren und Python String-Methoden.