Apprenez en lisant dans l'ordre

Expressions régulières re — Recherche et substitution de motifs

Apprends le module re de Python depuis la base. Couvre quand utiliser re.match / re.search / re.findall, comment combiner les métacaractères \d / \w / \s / * / + / ?, capturer des groupes avec ( ), substituer avec re.sub, et réutiliser des motifs avec re.compile — avec des exercices pratiques exécutables.

Cet article parcourt le module re pour les expressions régulières« extraire et remplacer les sous-chaînes qui correspondent à un motif spécifique ». Des choses que tu fais constamment dans des projets réels — analyser numéros de téléphone, emails, lignes de log et URLs — deviennent des one-liners.

Un outil pour tester du regex en direct

Les expressions régulières ont beaucoup d'éléments mobiles et sont difficiles à raisonner uniquement de tête. Pour vérifier que ton motif correspond à ce que tu attendais, le Regex Extractor tourne entièrement dans le navigateur — tape un motif et du texte et vois les correspondances en temps réel. Le garder ouvert à côté de cet article rend le suivi beaucoup plus facile.

match, search et findall — Trois fonctions de recherche et quand utiliser chacune

Le module re expose plusieurs fonctions de recherche, et tu choisis parmi trois selon le besoin. Les noms sont descriptifs — match matche au début, search cherche une correspondance n'importe où, et findall les trouve toutes. Les plages de recherche, types de retour et comportements sans correspondance sont résumés dans la prochaine table.

FonctionPlage de rechercheRetourSans correspondance
re.matchDébut de la chaîne uniquementMatch objectNone
re.searchPremière correspondance n'importe oùMatch objectNone
re.findallToutes les correspondancesListe de chaînesListe vide []

Du Match object retourné par re.match et re.search (un objet contenant la position du match, la chaîne matchée et les infos de groupe), tu lis la chaîne matchée en appelant sa méthode `.group()`m.group() ou m.group(0) pour le match complet, et (avec les groupes de capture introduits plus loin) m.group(1) pour juste ce qui était entre parenthèses. Seul re.findall retourne directement une liste, donc tu n'appelles pas .group() dessus.

Différences entre re.match / search / findall
re.matchmatch depuis le débutSi le début ne matche pas→ Nonere.searchpremier match n'importe oùMatch si trouvéNone sinonre.findalltoutes les correspondancesListe de chaînes matchées([] si aucune)
match vérifie seulement si le motif apparaît au début de la chaîne. search retourne la première correspondance à n'importe quelle position. findall retourne toutes les correspondances dans une liste.
MétacaractèreSensExemple
\dUn seul chiffre (0-9)\d+ → un ou plusieurs chiffres
\wUn caractère de mot (alphanumérique + underscore)\w+ → IDs et mots-clés
\sUn caractère blanc (espace / tab / saut de ligne)Séparateurs
.N'importe quel caractère sauf saut de ligneJoker
*Zéro ou plus du précédenta* → vide aussi OK
+Un ou plus du précédenta+ → au moins un
?Zéro ou un du précédentOptionnel
[abc]Un parmi a / b / cChoix
^ / $Début / fin de chaîneAncres
import re

text = "user_id: 12345, age: 30"

# match: depuis le début (\w+ est une suite de caractères de mot)
m = re.match(r"\w+", text)
print(m.group())            # user_id

# search: première suite de chiffres n'importe où
s = re.search(r"\d+", text)
print(s.group())            # 12345

# findall: chaque suite de chiffres
nums = re.findall(r"\d+", text)
print(nums)                 # ['12345', '30']

Écris regex comme une raw string r"..."

Les antislashs apparaissent partout dans regex. Une chaîne normale "\d" peut voir ses échappements interprétés par la couche string avant que re ne la voie, donc il est plus sûr d'écrire la raw string `r"\d"` avec le r au début. Les éditeurs ont aussi tendance à mettre en évidence les raw strings comme regex, ce qui améliore la lisibilité.

Sors un ID et des nombres d'une ligne de log. Essaie re.match / re.search / re.findall contre la même chaîne et observe comment les résultats diffèrent.

① Importe re.

② Définis text = "order_id: 9876, qty: 3, price: 1500".

③ Sors une suite de caractères de mot depuis le début de la chaîne et affiche-la sous match: ◯◯.

④ Sors la première suite de chiffres de la chaîne et affiche-la sous search: ◯◯.

⑤ Sors toutes les suites de chiffres comme une liste et affiche-la sous findall: ◯◯.

(Si ton code s'exécute correctement, l'explication apparaîtra.)

Éditeur Python

Exécuter le code pour voir le résultat

Groupes de capture — Sortir des parties spécifiques d'un motif

Tout ce que tu mets entre `( )` dans un regex devient un groupe de capture — au lieu de juste le match complet, tu peux sortir chaque morceau séparément. Des motifs comme r"#(\d+) on (\d{4})-(\d{2})-(\d{2})" te permettent d'extraire un numéro de commande et une date d'une ligne de log d'un coup.

Appelle `.group(N)` sur le Match object pour lire le N-ième groupe (numéroté à partir de 1). .group(0) (ou .group() sans argument) retourne le match complet.

Comment les groupes de capture fonctionnent
#(\d+) on(\d{4})-(\d{2})-(\d{2}).group(0)match complet.group(1)numéro de commande.group(2)année.group(3)mois
Chaque `( )` dans le regex devient un groupe, adressable par index 1-based comme .group(1) / .group(2). .group(0) est le match complet.
import re

text = "Order #1234 placed on 2024-03-15"

# Ce que veut dire le motif :
#   #         → '#' littéral
#   (\d+)     → un ou plusieurs chiffres → group(1) numéro de commande
#   placed on → 'placed on' littéral
#   (\d{4})   → 4 chiffres → group(2) année
#   (\d{2})   → 2 chiffres → group(3) mois
#   (\d{2})   → 2 chiffres → group(4) jour
m = re.search(r"#(\d+) placed on (\d{4})-(\d{2})-(\d{2})", text)
if m:
    print("whole:", m.group(0))    # #1234 placed on 2024-03-15
    print("order #:", m.group(1))   # 1234
    print("year:", m.group(2))      # 2024
    print("month:", m.group(3))     # 03
    print("day:", m.group(4))       # 15

Appeler .group() quand Match est None lève une erreur

Quand re.search ne trouve pas le motif il retourne None. Appeler m.group() dessus crashe avec AttributeError: 'NoneType' object has no attribute 'group'. Vérifie toujours avec `if m:` d'abord avant .group(), ou fais les deux en une étape avec l'opérateur morse : if m := re.search(...): ....

Sépare une adresse email en nom d'utilisateur et domaine. Utilise les groupes de capture pour sortir les deux parties dans une seule recherche.

① Importe re.

② Définis text = "contacte-nous à alice@example.com".

③ Écris un motif d'email qui capture ce qui est de chaque côté du @ comme groupes séparés.

- Gauche : caractères de mot plus ., +, -, un ou plus

- Droite : mêmes types de caractères, finissant par un domaine comme .com

④ Quand le match est trouvé, affiche `username: ◯◯` et `domain: ◯◯`.

Éditeur Python

Exécuter le code pour voir le résultat

re.sub — Remplacer des correspondances de motif

« Masquer des données personnelles d'un log », « enlever les balises HTML et garder le texte du corps », « normaliser un mélange d'espaces pleine largeur et demi-largeur » — ils reviennent tous à « réécrire tout ce qui correspond à un motif en autre chose ». Le replace de string ne gère que des sous-chaînes fixes, mais re.sub le fait par motif.

`re.sub(motif, remplacement, original)` retourne une nouvelle chaîne avec chaque correspondance remplacée par le remplacement. La chaîne originale est inchangée (les chaînes Python sont immuables, donc tu travailles toujours avec la valeur de retour).

Comment re.sub fonctionne
Chaîne originale"Tél : 03-1234-5678"re.sub(\d, *, ...)Nouvelle chaîne"Tél : **-****-****"
Retourne une nouvelle chaîne avec chaque correspondance remplacée par la chaîne de remplacement. L'original est immuable ; reçois le résultat via la valeur de retour.
import re

# Masquer les chiffres dans un numéro de téléphone (remplacer chaque \d par un *)
text = "Tel: 03-1234-5678"
masked = re.sub(r"\d", "*", text)
print(masked)
# Tel: **-****-****

# Enlever les balises HTML pour garder seulement le texte du corps
html = "<p>Bonjour <b>monde</b></p>"
plain = re.sub(r"<[^>]+>", "", html)
print(plain)
# Bonjour monde

Masque les chiffres de tout numéro de téléphone qui apparaît dans une ligne de log en les remplaçant par ``.**

① Importe re.

② Définis text = "Contact : 03-1234-5678 ou 090-9999-8888".

③ Utilise re.sub pour *remplacer chaque chiffre `\d` par un seul `**, et affiche le résultat sous masked: ◯◯`.

④ Affiche le text original à nouveau sous original: ◯◯ pour confirmer qu'il est inchangé (re.sub ne retourne qu'une nouvelle chaîne).

Éditeur Python

Exécuter le code pour voir le résultat

re.compile — Réutiliser un motif

Quand tu utilises le même regex à répétition, écrire re.search(r"...", text) encore et encore fait que le moteur parse (compile) le motif à chaque fois, ce qui est du travail inutile. `re.compile(motif)` construit un objet motif compilé une seule fois, et tu appelles des méthodes dessus comme pattern.search(...) / pattern.findall(...) / pattern.sub(...). Le code se lit mieux et tourne plus vite.

Comment re.compile est utilisé
r"\d{2,4}-\d{4}-\d{4}"re.compile(...)phone_re(objet motif)phone_re.search(text)phone_re.findall(text)phone_re.sub("*", text)
`re.compile(motif)` te donne un objet motif sur lequel tu peux appeler .search / .findall / .sub autant de fois que nécessaire. Compile quand tu réutilises le même motif.
import re

# Réutiliser le même motif de numéro de téléphone
phone_re = re.compile(r"\d{2,4}-\d{4}-\d{4}")

print(phone_re.findall("03-1234-5678 ou 080-1111-2222"))
# ['03-1234-5678', '080-1111-2222']

print(phone_re.search("mon téléphone est 03-9999-0000").group())
# 03-9999-0000

print(phone_re.sub("<phone>", "Contact: 03-1234-5678"))
# Contact: <phone>

Construis le motif de numéro de téléphone une seule fois avec `re.compile`, puis compte et substitue contre le même texte à la suite.

① Importe re.

② Définis text = "Contact : 03-1234-5678 ou 090-9999-8888".

③ Compile un motif de numéro de téléphone qui est 2-4 chiffres + 4 chiffres + 4 chiffres avec re.compile, et stocke-le comme phone_re.

④ Utilise phone_re.findall(text) pour compter combien de numéros de téléphone il y a, et affiche-le sous phone count: ◯.

⑤ Utilise phone_re.sub pour remplacer chaque numéro de téléphone entier par `<phone>`, et affiche le résultat sous replaced: ◯◯.

Éditeur Python

Exécuter le code pour voir le résultat
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Que retourne re.match(r"\d+", "abc 123") ?

Question 2Quel est le bon regex pour un ou plusieurs chiffres à la suite ?

Question 3À partir de re.search(r"(\w+)@(\w+)", "alice@example"), quel appel retourne juste le domaine ?

Question 4Quelle est la principale raison d'utiliser une raw string `r"..."` quand on écrit du regex en Python ?