Groupes de chaînes Python — Groupes de capture Regex
Apprenez comment fonctionnent les groupes de capture Python : group(), groups(), groupes nommés, non-capturants et rétroréférences avec exemples.
Lorsque le module re de Python trouve une correspondance dans une chaîne, il vous renvoie un objet de correspondance. L'objet de correspondance est bien plus qu'un simple résultat oui/non — il mémorise chaque sous-motif que vous avez entouré de parenthèses, appelé groupe de capture. Les méthodes .group() et .groups() permettent de récupérer ces éléments capturés.
Ce chapitre couvre tout ce dont vous avez besoin pour utiliser les groupes avec confiance : groupes numérotés, groupes nommés, groupes non-capturants, rétroréférences dans les substitutions, et pièges courants.
Qu'est-ce qu'un groupe de capture ?
Un groupe de capture est une partie d'un motif regex entourée de parenthèses simples (...). Lorsque le motif correspond, Python enregistre séparément le texte correspondant à chaque paire de parenthèses, en plus d'enregistrer la correspondance complète.
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 groupLes groupes sont numérotés de gauche à droite, en commençant à 1, dans l'ordre où leur parenthèse ouvrante apparaît. group(0) (ou simplement group() sans argument) retourne toujours la correspondance entière.
La méthode .group()
.group(n) retourne le texte correspondant au n-ième groupe de capture. Vous pouvez passer plusieurs indices à la fois pour obtenir un tuple de résultats.
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')Si un groupe existe dans le motif mais n'a pas participé à la correspondance (par exemple, il se trouvait dans une section optionnelle), group(n) retourne None.
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)) # 456La méthode .groups()
.groups() retourne un tuple contenant le texte correspondant à chaque groupe de capture, dans l'ordre. C'est un raccourci pratique lorsque vous voulez tous les groupes à la fois.
import re
m = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-03-15')
print(m.groups()) # ('2024', '03', '15')Fournir une valeur par défaut pour les groupes sans correspondance
Si un groupe n'a pas participé à la correspondance, il apparaît comme None dans le tuple. Vous pouvez fournir un argument default pour remplacer ces valeurs None par quelque chose de plus utile.
import re
m = re.search(r'(\d+)?-(\d+)', 'abc-456')
print(m.groups()) # (None, '456')
print(m.groups(default='0')) # ('0', '456')Groupes nommés avec (?P<name>...)
Les groupes numérotés deviennent difficiles à suivre dans les motifs complexes. Les groupes nommés vous permettent d'assigner une étiquette significative à chaque groupe en utilisant la syntaxe (?P<name>...), puis de récupérer la valeur par nom plutôt que par position.
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')) # 15Les groupes nommés ont toujours un numéro, vous pouvez donc y accéder de l'une ou l'autre façon.
La méthode .groupdict()
Lorsque vous avez des groupes nommés, .groupdict() retourne un dictionnaire associant chaque nom au texte correspondant. C'est utile lorsque vous voulez décomposer une correspondance en un objet structuré.
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'}Vous pouvez ensuite utiliser le résultat directement :
date_parts = m.groupdict()
print(f"{date_parts['day']}/{date_parts['month']}/{date_parts['year']}")
# 15/03/2024Groupes non-capturants avec (?:...)
Parfois, vous avez besoin de regrouper une partie d'un motif dans le but d'appliquer un quantificateur ou une alternance, mais vous ne souhaitez pas que ce groupe apparaisse dans les résultats de correspondance. Utilisez (?:...) — un groupe non-capturant.
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')Les groupes non-capturants constituent une bonne pratique lorsque vous n'avez pas besoin d'une sous-correspondance particulière — ils gardent vos indices de groupe propres et évitent le gaspillage de mémoire.
Groupes imbriqués
Les groupes peuvent être imbriqués. La numérotation est toujours basée sur la position de la parenthèse ouvrante, de gauche à droite.
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')Groupes avec re.findall()
Lorsque vous utilisez re.findall() et que le motif contient exactement un groupe de capture, il retourne une liste de chaînes — une par correspondance.
Lorsque le motif contient deux ou plus groupes de capture, il retourne une liste de tuples.
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')]Groupes avec re.finditer()
re.finditer() retourne un itérateur d'objets de correspondance, vous pouvez donc accéder à .group() et .groups() sur chacun individuellement. C'est l'approche la plus flexible lorsque vous avez besoin de tous les détails de correspondance pour chaque occurrence.
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étroréférences dans re.sub()
Les groupes de capture alimentent également les rétroréférences dans re.sub(). Dans la chaîne de remplacement, \1, \2, … font référence au texte capturé par le groupe 1, le groupe 2, et ainsi de suite. Les groupes nommés utilisent \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 d'un groupe : .start(), .end(), .span()
Les objets de correspondance exposent la position de chaque groupe, pas seulement son texte. Passez un numéro de groupe (ou un nom) à .start(), .end(), ou .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 7Référence rapide
| Méthode | Retourne | Notes |
|---|---|---|
m.group() ou m.group(0) | Chaîne de correspondance complète | Toujours disponible |
m.group(n) | Texte correspondant au groupe n | None si le groupe n'a pas correspondu |
m.group(n, m, ...) | Tuple de groupes | Plusieurs indices en un seul appel |
m.groups() | Tuple de tous les groupes | default= optionnel pour les non-correspondances |
m.groupdict() | Dict des groupes nommés | Seuls les groupes nommés apparaissent |
m.start(n) / m.end(n) | Index de début / fin du groupe n | Passez 0 pour la correspondance complète |
m.span(n) | Tuple (start, end) | Raccourci pour les deux |
Erreurs courantes
Utiliser .group() sans vérifier None. re.search() retourne None lorsqu'il n'y a pas de correspondance, donc appeler .group() directement sur le résultat lève une AttributeError. Protégez toujours l'appel :
import re
m = re.search(r'(\d+)', 'no digits here')
if m:
print(m.group(1))
else:
print('no match')
# no digits hereConfondre .group() et .groups(). .group(1) retourne une string ; .groups() retourne toujours un tuple. Les mélanger provoque des erreurs de type subtiles.
Oublier que re.findall() change de forme avec les groupes. Sans groupes, il retourne une liste plate de strings ; avec des groupes, il retourne une liste de tuples. Ajouter ou supprimer un groupe de capture peut faire échouer silencieusement le code qui traite le résultat.
Quand utiliser chaque approche
- Utilisez les groupes numérotés pour les motifs simples et courts avec peu de groupes.
- Utilisez les groupes nommés lorsque les motifs sont complexes, ou lorsque vous utiliserez le résultat sur plusieurs lignes de code — les noms servent d'auto-documentation.
- Utilisez les groupes non-capturants
(?:...)chaque fois que vous n'avez besoin du regroupement que pour appliquer un quantificateur ou une alternance, et non pour extraire une valeur. - Utilisez
.groups()pour décompresser toutes les captures à la fois dans un tuple. - Utilisez
.groupdict()lorsque tous les groupes sont nommés et que vous souhaitez un dictionnaire.
Pour un aperçu plus large du module re et de sa syntaxe de motif, consultez le chapitre Python RegEx. Pour les opérations sur les chaînes qui ne nécessitent pas de regex, consultez Modifier les chaînes et Méthodes de chaînes Python.