Les expressions régulières (ou regex) sont un mini-langage pour décrire des motifs de texte. Elles permettent de valider un email, extraire tous les numéros de téléphone d’un document, nettoyer un fichier mal formaté… Python expose cette puissance via le module standard re. Les regex sont un sujet majeur du niveau Expert TOSA.
🎯 Pourquoi c’est difficile (mais utile)
Les regex ont une syntaxe dense qu’on ne peut pas deviner : \d+, [^abc], (.+?)… Il faut les apprendre. La bonne nouvelle : une fois les bases acquises, c’est un outil utilisé toute votre vie de développeur.
41.1 Premier contact
Le module re fournit des fonctions pour chercher/manipuler des motifs dans des chaînes.
import retexte ="Contactez-nous au 04 90 12 34 56 ou au 06 11 22 33 44."# Chercher tous les numéros de téléphone (format XX XX XX XX XX)numeros = re.findall(r"\d{2} \d{2} \d{2} \d{2} \d{2}", texte)print(numeros)
['04 90 12 34 56', '06 11 22 33 44']
En 5 caractères spéciaux (\d{2}), on a extrait tous les numéros. C’est la puissance des regex.
Pourquoi le r"..." ?
Le préfixe r (raw string) désactive l’interprétation des \ par Python. Sans lui, "\d" serait interprété par Python avant d’arriver à regex, et certains motifs casseraient.
Règle absolue : toujours utiliser r"..." pour écrire un motif regex.
41.2 Les caractères de base
Caractères littéraux
Dans un motif, la plupart des caractères représentent eux-mêmes.
import reprint(re.findall(r"chat", "le chat et le chien"))print(re.findall(r"python", "Python n'est pas python")) # sensible à la casse
['chat']
['python']
Caractères spéciaux à connaître
Ces caractères ont un sens spécial et doivent être échappés avec \ pour être matchés littéralement :
. ^ $ * + ? { } [ ] ( ) \ |
Exemple : pour chercher un vrai point, il faut \. :
import reprint(re.findall(r"\.", "fin. début."))
['.', '.']
41.3 Les classes de caractères
Une classe représente un caractère appartenant à un ensemble.
Classes prédéfinies
Classe
Sens
\d
Un chiffre (0-9)
\D
Un non-chiffre
\w
Un caractère de mot : lettre, chiffre, underscore
\W
Un non-mot
\s
Un espace (espace, tabulation, newline…)
\S
Un non-espace
.
N’importe quel caractère (sauf \n par défaut)
import retexte ="Alice a 30 ans, Bob en a 25."print(re.findall(r"\d", texte)) # chiffres isolésprint(re.findall(r"\w+", texte)) # mots entiers
import re# Un caractère parmi a, e, i, o, uprint(re.findall(r"[aeiou]", "bonjour"))# Une plage : a-z, A-Z, 0-9print(re.findall(r"[A-Z]", "Alice et Bob ont 30 ans"))# Négation avec ^print(re.findall(r"[^aeiou ]", "bonjour")) # ni voyelles ni espaces
['o', 'o', 'u']
['A', 'B']
['b', 'n', 'j', 'r']
Dans une classe [...], beaucoup de caractères perdent leur sens spécial
[.+] matche un vrai point ou un vrai plus, pas « n’importe quoi ou 1+ ».
Seuls quelques caractères restent spéciaux dans [...] : ], \, -, ^.
41.4 Les quantificateurs
Les quantificateurs indiquent combien de fois un motif peut se répéter.
Quantificateur
Sens
*
0 ou plus
+
1 ou plus
?
0 ou 1 (optionnel)
{n}
Exactement n fois
{n,}
Au moins n
{n,m}
Entre n et m
import re# 1 ou plusieurs chiffresprint(re.findall(r"\d+", "abc 123 def 4567"))# Exactement 5 chiffres (code postal)print(re.findall(r"\d{5}", "zip 75001 ou 69002 (mais pas 999)"))# Entre 2 et 4 chiffresprint(re.findall(r"\d{2,4}", "1 22 333 4444 55555"))# "Ah" avec 0 ou plus de "h" supplémentairesprint(re.findall(r"Ahh*", "Ah Ahh Ahhh Aaah"))
Par défaut, les quantificateurs sont gourmands : ils matchent le maximum possible. Pour forcer une matching minimale, on ajoute un ?.
import retexte ="<b>gras</b> et <i>italique</i>"# Gourmand : capture TOUT jusqu'au dernier >print(re.findall(r"<.+>", texte))# Paresseux : s'arrête au premier >print(re.findall(r"<.+?>", texte))
['<b>gras</b> et <i>italique</i>']
['<b>', '</b>', '<i>', '</i>']
⚠️ Piège TOSA classique
.+ (gourmand) vs .+? (paresseux) est une question fréquemment posée. Retenez : le ? rend le quantificateur paresseux.
41.5 Ancres
Les ancres ne matchent pas des caractères, mais des positions.
Ancre
Position
^
Début de chaîne (ou ligne en mode multiligne)
$
Fin de chaîne (ou ligne)
\b
Frontière de mot
\B
Non-frontière de mot
import re# Doit commencer par une majusculeprint(re.findall(r"^[A-Z]\w+", "Alice est là"))print(re.findall(r"^[A-Z]\w+", "alice est là")) # rien# Mot entier "chat" (pas "chats" ni "chaton")print(re.findall(r"\bchat\b", "chat chats chaton"))# Mot qui finit par "-tion"print(re.findall(r"\w+tion\b", "action réaction autonomie direction"))
import retexte ="Alice a 30 ans, Bob en a 25."# Sans groupes : on obtient la correspondance complèteprint(re.findall(r"\w+ a \d+", texte))# Avec groupes : on obtient des tuplesprint(re.findall(r"(\w+) a (\d+)", texte))
['Alice a 30', 'en a 25']
[('Alice', '30'), ('en', '25')]
Chaque (...) crée un groupe capturé, accessible par son indice (1, 2, …).
Groupes non capturants : (?:...)
Si on veut juste grouper sans capturer, on utilise (?:...) :
import re# Grouper pour l'alternative, sans capturertexte ="J'aime les chats et les chiens"print(re.findall(r"(?:chat|chien)s?", texte))
['chats', 'chiens']
Alternative : |
Le pipe| signifie « ou » dans un motif.
import reprint(re.findall(r"chat|chien", "chat chien poisson"))print(re.findall(r"ro(uge|se)", "rouge rose noir")) # capture la partie variable
Similaire à search, mais uniquement au début de la chaîne.
import reprint(re.match(r"\d+", "123abc")) # OKprint(re.match(r"\d+", "abc123")) # None : ne commence pas par chiffre
<re.Match object; span=(0, 3), match='123'>
None
match vs search
re.match(r"motif", s) ≈ re.search(r"^motif", s).
En pratique, search est plus fréquent. Utilisez match seulement si vous voulez explicitement l’ancre au début.
re.findall — toutes les occurrences
Renvoie une liste. Si le motif contient des groupes, renvoie les tuples des groupes.
import re# Sans groupes : liste de chaînesprint(re.findall(r"\d+", "a1 b22 c333"))# Avec groupes : liste de tuplesprint(re.findall(r"(\w+)=(\d+)", "age=30 taille=180 poids=75"))
Utile quand on veut les positions ou les détails des matches :
import retexte ="Alice a 30, Bob a 25, Charlie a 28."for m in re.finditer(r"(\w+) a (\d+)", texte):print(f"'{m.group(0)}' à la position {m.start()} — nom={m.group(1)}, age={m.group(2)}")
'Alice a 30' à la position 0 — nom=Alice, age=30
'Bob a 25' à la position 12 — nom=Bob, age=25
'Charlie a 28' à la position 22 — nom=Charlie, age=28
re.sub — substitution
Remplace les matches par une chaîne.
import retexte ="Mon tél : 01-23-45-67-89"# Remplacer les tirets par des espacesprint(re.sub(r"-", " ", texte))# Remplacer par une fonction (plus puissant)def anonymiser(m):return"X"*len(m.group())print(re.sub(r"\d", anonymiser, "Mon tél : 04 90 12 34 56"))
Mon tél : 01 23 45 67 89
Mon tél : XX XX XX XX XX
Référence aux groupes dans la substitution
On peut référencer les groupes capturés dans la chaîne de remplacement avec \1, \2, etc.
import re# Inverser prénom et nomtexte ="Marie Curie, Isaac Newton, Albert Einstein"print(re.sub(r"(\w+) (\w+)", r"\2 \1", texte))
Curie Marie, Newton Isaac, Einstein Albert
re.split — découper selon un motif
import re# Découper selon un ou plusieurs espaces/tabulationstexte ="mot1 mot2\tmot3 mot4"print(re.split(r"\s+", texte))# Découper selon plusieurs séparateursprint(re.split(r"[,;:]", "a,b;c:d"))
La « vraie » regex pour valider un email selon la RFC 5322 fait plusieurs centaines de caractères. La regex ci-dessus est suffisante en pratique pour 99 % des cas, mais ne couvre pas tous les emails légaux (ex: adresses avec caractères internationaux).
Règle pragmatique : utilisez une regex simple pour filtrer les typos évidents, puis envoyez un email de confirmation.
Extraire des numéros de téléphone français
import retexte ="""Mon numéro : 06 11 22 33 44Bureau : 01.23.45.67.89International : +33 6 78 90 12 34Urgence : 112"""# Motif pour numéros français (format flexible)motif = re.compile(r"(?:\+33\s?|0)\d(?:[\s.-]?\d{2}){4}")numeros = motif.findall(texte)print("Numéros trouvés :")for n in numeros:print(f" {n}")
import rehtml ="<p>Bonjour <b>Alice</b>, comment <i>ça va</i> ?</p>"# Supprimer toutes les balisestexte_propre = re.sub(r"<[^>]+>", "", html)print(texte_propre)
Bonjour Alice, comment ça va ?
Extraire des dates
import retexte ="Réunion le 15/03/2026, puis le 22-04-2026 et enfin le 2026-05-10."# Format flexible avec - ou /motif = re.compile(r"\d{1,2}[/-]\d{1,2}[/-]\d{2,4}|\d{4}-\d{2}-\d{2}")print(motif.findall(texte))
['15/03/2026', '22-04-2026', '2026-05-10']
41.11 Tester ses regex
Des outils en ligne facilitent l’apprentissage :
regex101.com : éditeur avec explication du motif.
regexr.com : similaire, avec bibliothèque de patterns.
Mettre son motif, voir ce qu’il match, c’est ultra-pédagogique. Utilisez ces outils pour apprendre.
🧩 Quiz 1.1 — Regex
Question 1
Que matche \d+ ?
Un chiffre unique
Un ou plusieurs chiffres
Zéro ou plusieurs chiffres
N’importe quel caractère
🔍 Réponse
b) Un ou plusieurs chiffres — + signifie « au moins 1 ».
import retexte ="""Visitez https://python.org pour la documentation,et http://regex101.com pour tester vos regex.Pas valide : ftp://exemple.com"""urls = ...print(urls)
import retexte ="""Visitez https://python.org pour la documentation,et http://regex101.com pour tester vos regex.Pas valide : ftp://exemple.com"""motif = re.compile(r"https?://\S+")urls = motif.findall(texte)print(urls)
['https://python.org', 'http://regex101.com']
https? : http suivi de s optionnel (le ? sur le s précédent).
:// : littéral.
\S+ : un ou plusieurs caractères non-espace (on s’arrête au premier espace).
Amélioration possible : retirer la ponctuation finale éventuelle (,, .) avec un motif plus fin :
import retexte ="Voir https://python.org, puis https://exemple.fr."motif = re.compile(r"https?://[\w.-]+(?:/\S*)?")print(motif.findall(texte))
['https://python.org', 'https://exemple.fr.']
✏️ Exercice 1.3 — Anonymiser des téléphones
Remplacez tous les numéros de téléphone français dans un texte par XX XX XX XX XX.
import retexte ="""Appelle-moi au 06 11 22 33 44 ou au 01-23-45-67-89.En urgence : 112 (pas à remplacer).Fixe : 04.90.12.34.56"""resultat = ...print(resultat)
import retexte ="""Appelle-moi au 06 11 22 33 44 ou au 01-23-45-67-89.En urgence : 112 (pas à remplacer).Fixe : 04.90.12.34.56"""# Numéro français = 10 chiffres, groupés 2 par 2, séparateurs variablesmotif = re.compile(r"0\d(?:[\s.-]?\d{2}){4}")resultat = motif.sub("XX XX XX XX XX", texte)print(resultat)
Appelle-moi au XX XX XX XX XX ou au XX XX XX XX XX.
En urgence : 112 (pas à remplacer).
Fixe : XX XX XX XX XX
0\d : premier groupe de 2 chiffres (commence toujours par 0).
(?:[\s.-]?\d{2}){4} : 4 fois :
[\s.-]? : séparateur optionnel (espace, point ou tiret).
\d{2} : 2 chiffres.
(?:...) : groupe non-capturant (on n’a pas besoin de récupérer les parties).
Le 112 n’est pas remplacé car il ne fait que 3 chiffres.
✏️ Exercice 1.4 — Compter les mots d’un texte
Écrivez une fonction compter_mots(texte) qui renvoie un dict {mot: occurrences}, en ignorant la ponctuation et la casse.