// Python

Les expressions régulières en Python (module re)

Une expression régulière (ou regex) est un motif qui décrit un ensemble de chaînes de caractères. En Python, tout passe par le module re de la bibliothèque standard : il permet de rechercher, extraire, valider et remplacer du texte avec une précision chirurgicale. Ce guide couvre le sujet de façon exhaustive : les fonctions principales (match, search, findall, sub), la syntaxe des motifs (classes, quantificateurs, ancres, groupes), les groupes nommés, les flags, et des exemples concrets comme la validation d’emails et l’extraction de dates.

Le module re et les chaînes brutes

On commence toujours par importer re. Les motifs s’écrivent dans des chaînes brutes (préfixe r"..."), qui empêchent Python d’interpréter les antislashs — essentiel car les regex en utilisent beaucoup.

import re

motif = r"\d+"   # r"" = chaîne brute, \d n'est pas échappé par Python
texte = "Il y a 42 chats et 7 chiens"

resultat = re.findall(motif, texte)
print(resultat)   # ['42', '7']

Sans le r, il faudrait écrire "\\d+". Prenez l’habitude d’utiliser r"..." systématiquement pour vos motifs.

re.search : trouver la première correspondance

re.search(motif, texte) parcourt toute la chaîne et renvoie un objet Match à la première correspondance, ou None si aucune.

import re

texte = "Commande n°A123 expédiée"
resultat = re.search(r"[A-Z]\d+", texte)

if resultat:
    print(resultat.group())   # A123
    print(resultat.start())   # 11 (position de départ)

L’objet Match est « vrai » dans un test if, ce qui rend la vérification très lisible.

re.match : ancrer au début

re.match ne teste le motif qu’au tout début de la chaîne. Il renvoie None si la correspondance n’est pas en position zéro.

import re

print(re.match(r"\d+", "123 abc"))   # correspondance
print(re.match(r"\d+", "abc 123"))   # None (ne commence pas par un chiffre)

Retenez la différence : match = « commence par », search = « contient quelque part ».

re.findall et re.finditer : toutes les correspondances

re.findall renvoie la liste de toutes les correspondances. re.finditer renvoie un itérateur d’objets Match, utile quand on a besoin des positions.

import re

texte = "prix : 12€, 8€ et 150€"

print(re.findall(r"\d+", texte))   # ['12', '8', '150']

for m in re.finditer(r"\d+", texte):
    print(m.group(), "à la position", m.start())

Attention : si le motif contient des groupes (...), findall renvoie les groupes et non la correspondance complète — un piège fréquent que nous illustrons plus bas.

re.sub : remplacer

re.sub(motif, remplacement, texte) remplace toutes les correspondances par la chaîne fournie.

import re

texte = "Contactez-nous au 06 12 34 56 78 svp"
masque = re.sub(r"\d", "X", texte)
print(masque)   # Contactez-nous au XX XX XX XX XX svp

Le remplacement peut aussi être une fonction, pour un traitement dynamique :

import re

def majuscule(m):
    return m.group().upper()

print(re.sub(r"\b\w", majuscule, "bonjour le monde"))   # Bonjour Le Monde

La syntaxe des motifs : classes de caractères

Une classe de caractères décrit un ensemble de caractères acceptables.

Les classes prédéfinies

  • \d : un chiffre (0-9)
  • \w : un caractère de mot (lettre, chiffre, underscore)
  • \s : un espace, une tabulation ou un retour à la ligne
  • . : n’importe quel caractère (sauf saut de ligne)

Leurs versions majuscules \D, \W, \S désignent l’inverse.

import re

print(re.findall(r"\w+", "hello_42 monde !"))   # ['hello_42', 'monde']
print(re.findall(r"\S+", "a b   c"))            # ['a', 'b', 'c']

Les classes personnalisées avec [ ]

Entre crochets, on définit son propre ensemble. Un tiret indique un intervalle, un ^ en tête signifie la négation.

import re

print(re.findall(r"[aeiou]", "programmation"))   # voyelles
print(re.findall(r"[A-Za-z]+", "Python 3.12"))   # ['Python']
print(re.findall(r"[^0-9]", "a1b2"))             # tout sauf des chiffres

Les quantificateurs

Ils indiquent combien de fois l’élément précédent doit apparaître.

  • * : zéro fois ou plus
  • + : une fois ou plus
  • ? : zéro ou une fois (optionnel)
  • {n} : exactement n fois
  • {n,m} : entre n et m fois
import re

print(re.findall(r"ab*", "a ab abb abbb"))   # ['a', 'ab', 'abb', 'abbb']
print(re.findall(r"\d{2,4}", "1 22 333 44444"))   # ['22', '333', '4444']

Par défaut les quantificateurs sont gloutons (ils prennent le maximum). Ajouter ? les rend paresseux :

import re

print(re.findall(r"<.*>", "<a><b>"))    # ['<a><b>']  (glouton)
print(re.findall(r"<.*?>", "<a><b>"))   # ['<a>', '<b>']  (paresseux)

Les ancres

Les ancres ne correspondent pas à des caractères mais à des positions.

  • ^ : début de chaîne (ou de ligne avec le flag MULTILINE)
  • $ : fin de chaîne
  • \b : frontière de mot
import re

print(re.search(r"^Bonjour", "Bonjour tout le monde"))   # correspond
print(re.search(r"monde$", "Bonjour le monde"))          # correspond
print(re.findall(r"\bchat\b", "chat chaton chat"))       # ['chat', 'chat']

Le \b évite ici de capturer « chat » à l’intérieur de « chaton ».

Les groupes de capture

Les parenthèses (...) créent un groupe dont on peut récupérer le contenu séparément avec .group(n).

import re

texte = "2025-12-02"
m = re.search(r"(\d{4})-(\d{2})-(\d{2})", texte)

print(m.group(0))   # 2025-12-02  (correspondance complète)
print(m.group(1))   # 2025  (année)
print(m.group(2))   # 12    (mois)
print(m.groups())   # ('2025', '12', '02')

Les groupes nommés

Pour un code plus lisible, on nomme les groupes avec (?P<nom>...) et on y accède par leur nom.

import re

texte = "2025-12-02"
m = re.search(r"(?P<annee>\d{4})-(?P<mois>\d{2})-(?P<jour>\d{2})", texte)

print(m.group("annee"))   # 2025
print(m.group("mois"))    # 12
print(m.groupdict())      # {'annee': '2025', 'mois': '12', 'jour': '02'}

C’est particulièrement précieux dans les motifs complexes où compter les parenthèses devient pénible.

Les flags

Les flags modifient le comportement global du motif. On les passe en dernier argument.

  • re.IGNORECASE (ou re.I) : ignore la casse
  • re.MULTILINE (re.M) : ^ et $ s’appliquent à chaque ligne
  • re.DOTALL (re.S) : . inclut aussi les sauts de ligne
import re

print(re.findall(r"python", "Python PYTHON python", re.IGNORECASE))
# ['Python', 'PYTHON', 'python']

texte = "ligne1\nligne2\nligne3"
print(re.findall(r"^ligne\d", texte, re.MULTILINE))
# ['ligne1', 'ligne2', 'ligne3']

Compiler un motif avec re.compile

Si vous réutilisez un même motif de nombreuses fois, re.compile le pré-compile une seule fois et améliore la lisibilité.

import re

motif_tel = re.compile(r"\d{2} \d{2} \d{2} \d{2} \d{2}")

for ligne in ["06 12 34 56 78", "rien ici"]:
    if motif_tel.search(ligne):
        print("Numéro trouvé :", ligne)

Exemple complet n°1 : valider un email

Un motif d’email pragmatique (aucune regex ne couvre parfaitement la norme, mais celle-ci suffit largement en pratique).

import re

motif_email = re.compile(r"^[\w.+-]+@[\w-]+\.[\w.-]+$")

for adresse in ["[email protected]", "invalide@", "[email protected]"]:
    if motif_email.match(adresse):
        print(adresse, "→ valide")
    else:
        print(adresse, "→ invalide")

Exemple complet n°2 : extraire des dates

On extrait toutes les dates au format JJ/MM/AAAA d’un texte, avec des groupes nommés.

import re

texte = "Réunion le 02/12/2025, livraison le 15/01/2026."
motif = re.compile(r"(?P<jour>\d{2})/(?P<mois>\d{2})/(?P<annee>\d{4})")

for m in motif.finditer(texte):
    print(f"{m.group('jour')}-{m.group('mois')}-{m.group('annee')}")

Sortie :

02-12-2025
15-01-2026

Conclusion

Le module re couvre tous les besoins de manipulation de texte par motif : search et match pour trouver, findall et finditer pour tout collecter, sub pour remplacer. Maîtrisez les classes de caractères, les quantificateurs et les groupes (nommés de préférence), et vous validerez emails, dates ou numéros de téléphone en quelques lignes. Pour des transformations plus simples, pensez aussi aux méthodes natives présentées dans notre guide sur la manipulation de chaînes en Python.

À lire ensuite