W3docs

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 group

Les 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))  # 456

La 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'))    # 15

Les 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/2024

Groupes 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   -> yahoo

Ré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 Jane

Position 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 7

Référence rapide

MéthodeRetourneNotes
m.group() ou m.group(0)Chaîne de correspondance complèteToujours disponible
m.group(n)Texte correspondant au groupe nNone si le groupe n'a pas correspondu
m.group(n, m, ...)Tuple de groupesPlusieurs indices en un seul appel
m.groups()Tuple de tous les groupesdefault= optionnel pour les non-correspondances
m.groupdict()Dict des groupes nommésSeuls les groupes nommés apparaissent
m.start(n) / m.end(n)Index de début / fin du groupe nPassez 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 here

Confondre .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.

Pratique

Pratique
In Python, which functions or methods can be used to define a group within strings?
In Python, which functions or methods can be used to define a group within strings?
Was this page helpful?