Python RegEx
Apprenez les expressions régulières Python : syntaxe, séquences spéciales, groupes, lookaheads, drapeaux et fonctions du module re avec des exemples clairs.
Les expressions régulières (regex) vous permettent de rechercher, d'extraire et de remplacer du texte à partir de motifs flexibles plutôt que de chaînes exactes. Le module re intégré à Python fournit tout ce dont vous avez besoin. Ce chapitre couvre l'ensemble de la boîte à outils regex : syntaxe, séquences spéciales, quantificateurs, groupes, lookahead/lookbehind, drapeaux et toutes les fonctions clés du module re — avec des exemples corrects et exécutables tout au long.
Pourquoi utiliser les expressions régulières ?
Les méthodes de string simples (str.find(), str.replace(), str.split()) fonctionnent bien pour du texte fixe. Les expressions régulières sont particulièrement utiles lorsque le motif varie :
- Valider qu'une string ressemble à une adresse e-mail ou à un numéro de téléphone.
- Extraire toutes les dates d'un document, quel que soit le format exact.
- Supprimer les balises HTML d'une string.
- Remplacer plusieurs séquences d'espaces différentes par un seul espace.
Lorsque la tâche consiste à décrire une forme plutôt qu'une valeur fixe, utilisez re.
Chaînes brutes (Raw Strings)
Les motifs regex sont presque toujours écrits sous forme de chaînes brutes (r'...'). Les chaînes brutes traitent les barres obliques inverses comme des caractères littéraux, ce qui est important car les regex utilisent de nombreuses séquences avec barre oblique inverse (\d, \w, \s, \b). Sans le préfixe r, vous auriez besoin de doubler les barres obliques inverses partout :
import re
# Both patterns are identical — raw string is easier to read
re.findall(r'\d+', 'abc 123') # raw: r'\d+'
re.findall('\\d+', 'abc 123') # normal: '\\d+'Utilisez des chaînes brutes pour chaque motif regex — c'est la convention universelle.
Syntaxe de base : métacaractères
Les métacaractères sont des caractères ayant une signification spéciale à l'intérieur d'un motif. Les caractères littéraux se correspondent exactement à eux-mêmes.
| Métacaractère | Signification |
|---|---|
. | N'importe quel caractère sauf un saut de ligne |
^ | Début de la string (ou de la ligne en mode MULTILINE) |
$ | Fin de la string (ou de la ligne en mode MULTILINE) |
* | Zéro ou plusieurs occurrences de l'élément précédent |
+ | Une ou plusieurs occurrences de l'élément précédent |
? | Zéro ou une occurrence de l'élément précédent |
{n} | Exactement n répétitions |
{n,m} | Entre n et m répétitions |
[...] | Classe de caractères — n'importe quel caractère listé à l'intérieur |
[^...] | Classe niée — n'importe quel caractère non listé |
| | Alternation — expression gauche ou droite |
() | Groupe capturant |
\ | Échapper un métacaractère ou démarrer une séquence spéciale |
Pour faire correspondre un métacaractère littéral comme . ou *, échappez-le avec une barre oblique inverse : \. correspond à un vrai point.
Séquences spéciales
Les séquences spéciales sont des classes de caractères abrégées qui apparaissent fréquemment dans les motifs réels.
| Séquence | Correspond à |
|---|---|
\d | N'importe quel chiffre — équivalent à [0-9] |
\D | N'importe quel caractère non-chiffre |
\w | Caractère de mot : lettres, chiffres, underscore |
\W | Caractère non-mot |
\s | Espace blanc : espace, tabulation, saut de ligne |
\S | Caractère non-espace |
\b | Limite de mot (largeur nulle) |
\B | Non-limite de mot |
import re
print(re.findall(r'\d+', 'I have 3 cats and 12 dogs'))
# ['3', '12']
print(re.findall(r'\w+', 'hello_world 123'))
# ['hello_world', '123']
print(re.findall(r'\bPython\b', 'Python Pythonista Python3'))
# ['Python'] — word boundary prevents partial matches\b est particulièrement utile : il correspond à la position entre un caractère de mot et un caractère non-mot, donc \bPython\b correspond au mot autonome « Python » mais pas à « Pythonista » ou « Python3 ».
Quantificateurs
Les quantificateurs contrôlent le nombre de fois que l'élément précédent doit correspondre.
import re
print(re.findall(r'a*', 'baaa')) # ['', 'aaa', '']
print(re.findall(r'a+', 'baaa')) # ['aaa']
print(re.findall(r'a?', 'baaa')) # ['', 'a', 'a', 'a', '']
print(re.findall(r'a{3}', 'baaa')) # ['aaa']Correspondance gourmande vs paresseuse
Par défaut, les quantificateurs sont gourmands — ils correspondent à autant de texte que possible. Ajoutez ? après le quantificateur pour le rendre paresseux (correspondance minimale possible).
import re
html = '<b>bold</b> and <i>italic</i>'
print(re.findall(r'<.*>', html))
# ['<b>bold</b> and <i>italic</i>'] — greedy: matches from first < to last >
print(re.findall(r'<.*?>', html))
# ['<b>', '</b>', '<i>', '</i>'] — lazy: matches each individual tagLes quantificateurs paresseux sont essentiels pour analyser du texte structuré comme HTML ou des fragments JSON.
Classes de caractères
Une classe de caractères [...] correspond à n'importe quel caractère de l'ensemble listé. Utilisez - pour les plages et ^ au début pour nier la classe.
import re
print(re.findall(r'[aeiou]', 'hello world'))
# ['e', 'o', 'o']
print(re.findall(r'[0-9]', 'a1b2c3'))
# ['1', '2', '3']
print(re.findall(r'[^aeiou\s]+', 'hello world'))
# ['h', 'll', 'w', 'rld'] — consonants only (not vowels, not spaces)Plages prédéfinies courantes : [a-z] lettres minuscules, [A-Z] majuscules, [0-9] chiffres, [a-zA-Z0-9] alphanumériques.
Ancres
Les ancres ne consomment pas de caractères — elles affirment une position dans la string.
import re
print(re.findall(r'^Python', 'Python is great'))
# ['Python'] — matches only if 'Python' is at the start
print(re.findall(r'great$', 'Python is great'))
# ['great'] — matches only if 'great' is at the end
print(re.findall(r'^Python', 'Learn Python'))
# [] — 'Python' is not at the start of this stringConsultez le drapeau re.MULTILINE plus loin dans ce chapitre pour appliquer ^ et $ par ligne plutôt que par string.
Alternation
Le pipe | fonctionne comme un OU logique entre deux expressions.
import re
print(re.findall(r'cat|dog', 'I have a cat and a dog'))
# ['cat', 'dog']
print(re.findall(r'colou?r|colour', 'color and colour'))
# ['color', 'colour']Groupes capturants
Les parenthèses () créent un groupe capturant. Les groupes vous permettent d'extraire des sous-parties d'une correspondance. re.search() renvoie un objet de correspondance ; appelez .group(n) ou .groups() dessus.
import re
match = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2023-10-05')
if match:
print(match.group(0)) # '2023-10-05' — entire match
print(match.group(1)) # '2023'
print(match.groups()) # ('2023', '10', '05')Groupes nommés
Nommez un groupe avec (?P<name>...) pour y accéder par son nom plutôt que par sa position. Cela rend les motifs beaucoup plus faciles à maintenir.
import re
match = re.search(
r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
'2023-10-05'
)
if match:
print(match.group('year')) # '2023'
print(match.groupdict()) # {'year': '2023', 'month': '10', 'day': '05'}Groupes non-capturants
Utilisez (?:...) lorsque vous avez besoin de grouper sans capturer la valeur.
import re
print(re.findall(r'(?:Mr|Mrs|Ms)\.? \w+', 'Mr. Smith and Mrs. Jones'))
# ['Mr. Smith', 'Mrs. Jones']Lookahead et Lookbehind
Le lookahead ((?=...)) et le lookbehind ((?<=...)) vérifient qu'un élément suit ou précède la correspondance, sans l'inclure dans le résultat. Ils sont de largeur nulle : ils ne consomment aucun caractère.
import re
# Positive lookahead — find numbers followed by 'dollars'
print(re.findall(r'\d+(?= dollars)', '100 dollars and 200 euros'))
# ['100']
# Negative lookahead — find numbers NOT followed by 'dollars'
print(re.findall(r'\b\d+\b(?! dollars)', '100 dollars and 200 euros'))
# ['200']
# Positive lookbehind — extract domain from email addresses
emails = 'Contact [email protected] or [email protected]'
print(re.findall(r'(?<=@)\w+\.\w+', emails))
# ['example.com', 'test.org']Les motifs de lookbehind doivent avoir une largeur fixe en Python — vous ne pouvez pas utiliser * ou + à l'intérieur.
Le module re : fonctions principales
re.search() — Trouver la première correspondance
Renvoie un objet de correspondance pour le premier emplacement où le motif correspond, ou None s'il n'y a pas de correspondance.
import re
text = 'apple banana apple'
match = re.search(r'banana', text)
if match:
print(match.group()) # 'banana'
print(match.span()) # (6, 12)re.match() — Correspondance au début uniquement
Comme re.search(), mais le motif doit correspondre au début de la string.
import re
print(bool(re.match(r'\d+', 'abc123'))) # False — no digit at start
print(bool(re.search(r'\d+', 'abc123'))) # True — digit found anywhereUtilisez re.search() lorsque vous voulez trouver un motif n'importe où ; utilisez re.match() lorsque le motif doit apparaître au début.
re.fullmatch() — Correspondance sur toute la string
Le motif doit correspondre à la totalité de la string du début à la fin.
import re
print(bool(re.fullmatch(r'\d{5}', '12345'))) # True — exactly 5 digits
print(bool(re.fullmatch(r'\d{5}', '123456'))) # False — too long
print(bool(re.fullmatch(r'\d{5}', '1234X'))) # False — non-digit presentre.fullmatch() est idéal pour la validation des saisies (codes postaux, numéros de téléphone, etc.).
re.findall() — Toutes les correspondances non-chevauchantes
Renvoie une liste de toutes les correspondances. Si le motif contient des groupes, renvoie une liste de tuples.
import re
print(re.findall(r'\d+', 'abc 123 def 456'))
# ['123', '456']
# With a group — returns list of group values
print(re.findall(r'(\w+)@(\w+\.\w+)', '[email protected] [email protected]'))
# [('alice', 'example.com'), ('bob', 'test.org')]re.finditer() — Itérateur d'objets de correspondance
Comme re.findall(), mais produit des objets de correspondance un à la fois. Utile pour les grands textes ou lorsque vous avez besoin des informations de position.
import re
for m in re.finditer(r'\d+', 'abc 123 def 456'):
print(m.group(), m.start(), m.end())
# 123 4 7
# 456 12 15re.sub() — Remplacer les correspondances
Remplace chaque occurrence du motif par une string de remplacement ou la valeur de retour d'une fonction.
import re
text = 'apple banana apple'
print(re.sub(r'apple', 'orange', text))
# 'orange banana orange'
# Limit replacements
print(re.sub(r'apple', 'orange', text, count=1))
# 'orange banana apple'
# Backreferences in replacement — reformat a date
print(re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', '2023-10-05'))
# '05/10/2023're.split() — Diviser par un motif
Divise la string à chaque occurrence du motif.
import re
print(re.split(r',\s*', 'apple, banana, cherry, date'))
# ['apple', 'banana', 'cherry', 'date']
# Limit the number of splits
print(re.split(r',\s*', 'apple, banana, cherry, date', maxsplit=2))
# ['apple', 'banana', 'cherry, date']Compiler des motifs avec re.compile()
Lorsque vous utilisez le même motif plusieurs fois, compilez-le une fois avec re.compile() pour améliorer les performances. L'objet motif résultant dispose de toutes les mêmes méthodes (findall, search, sub, etc.).
import re
# Compile once
digit_pattern = re.compile(r'\d+')
# Reuse many times
print(digit_pattern.findall('abc 123 def 456')) # ['123', '456']
print(digit_pattern.sub('NUM', 'abc 123 def 456')) # 'abc NUM def NUM'
print(digit_pattern.search('xyz 99')) # <re.Match object ...>La compilation est particulièrement utile à l'intérieur de boucles ou de fonctions appelées fréquemment.
Drapeaux (Flags)
Les drapeaux modifient le comportement de la correspondance. Passez-les comme dernier argument à n'importe quelle fonction re, ou incluez-les lors de l'appel à re.compile(). Plusieurs drapeaux peuvent être combinés avec |.
| Drapeau | Forme abrégée | Effet |
|---|---|---|
re.IGNORECASE | re.I | Correspondance insensible à la casse |
re.MULTILINE | re.M | ^ et $ correspondent au début/fin de chaque ligne |
re.DOTALL | re.S | . correspond aussi aux sauts de ligne |
re.VERBOSE | re.X | Autoriser les espaces et commentaires dans le motif |
import re
# IGNORECASE
print(re.findall(r'hello', 'Hello HELLO hello', re.IGNORECASE))
# ['Hello', 'HELLO', 'hello']
# MULTILINE — ^ matches the start of each line
text = 'first line\nsecond line\nthird line'
print(re.findall(r'^\w+', text, re.MULTILINE))
# ['first', 'second', 'third']
# DOTALL — . matches newline characters
print(re.findall(r'<.*?>', '<div>\n<p>text</p>\n</div>', re.DOTALL))
# ['<div>', '<p>', '</p>', '</div>']
# VERBOSE — write readable patterns with comments
date_pattern = re.compile(r'''
(?P<year>\d{4}) # four-digit year
-
(?P<month>\d{2}) # two-digit month
-
(?P<day>\d{2}) # two-digit day
''', re.VERBOSE)
print(date_pattern.search('2023-10-05').groupdict())
# {'year': '2023', 'month': '10', 'day': '05'}Échapper les saisies utilisateur avec re.escape()
Si vous construisez un motif à partir d'un texte fourni par l'utilisateur, échappez-le toujours en premier pour éviter une interprétation involontaire des métacaractères.
import re
user_input = 'hello.world'
# Without escaping, '.' matches any character
# With escaping, '\.' matches a literal dot
safe_pattern = re.escape(user_input)
print(safe_pattern) # 'hello\\.world'
print(bool(re.search(safe_pattern, 'say hello.world'))) # True
print(bool(re.search(safe_pattern, 'say helloXworld'))) # FalseExemples pratiques
Valider une adresse e-mail
import re
def is_valid_email(email):
pattern = r'^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$'
return bool(re.fullmatch(pattern, email))
print(is_valid_email('[email protected]')) # True
print(is_valid_email('bad-email@')) # FalseExtraire toutes les URL d'un texte
import re
text = 'Visit https://www.example.com or http://test.org for details.'
urls = re.findall(r'https?://[\w./-]+', text)
print(urls)
# ['https://www.example.com', 'http://test.org']Supprimer les espaces superflus
import re
messy = ' hello world Python '
clean = re.sub(r'\s+', ' ', messy).strip()
print(clean) # 'hello world Python'Analyser une ligne de journal
import re
log = '2023-10-05 14:32:11 ERROR Failed to connect to database'
pattern = re.compile(
r'(?P<date>\d{4}-\d{2}-\d{2}) '
r'(?P<time>\d{2}:\d{2}:\d{2}) '
r'(?P<level>\w+) '
r'(?P<message>.+)'
)
m = pattern.match(log)
if m:
print(m.group('level')) # 'ERROR'
print(m.group('message')) # 'Failed to connect to database'Pièges courants
re.match() vs re.search() — re.match() vérifie uniquement le début de la string. Les nouveaux utilisateurs s'attendent souvent à ce qu'il recherche n'importe où et sont confus lorsqu'il renvoie None.
Oublier les chaînes brutes — '\d' dans une string Python normale est simplement 'd' avec un échappement non reconnu (ou un SyntaxWarning en Python 3.12+). Écrivez toujours r'\d'.
Les correspondances gourmandes qui capturent trop — Si un motif .* capture plus que prévu, passez à .*? (paresseux).
Caractères spéciaux dans les strings de remplacement — Dans re.sub(), les séquences avec barre oblique inverse comme \1 dans le remplacement font référence aux groupes capturés. Pour inclure une barre oblique inverse littérale dans le remplacement, écrivez \\.
re.findall() avec des groupes — Lorsque votre motif contient des groupes, re.findall() renvoie les groupes, pas la correspondance complète. Utilisez un groupe non-capturant (?:...) si vous voulez la correspondance complète.
Référence rapide
| Tâche | Fonction |
|---|---|
| Trouver la première correspondance | re.search(pattern, text) |
| Trouver toutes les correspondances | re.findall(pattern, text) |
| Itérer les correspondances | re.finditer(pattern, text) |
| Correspondance au début | re.match(pattern, text) |
| Correspondance sur toute la string | re.fullmatch(pattern, text) |
| Remplacer | re.sub(pattern, repl, text) |
| Diviser | re.split(pattern, text) |
| Compiler pour réutilisation | re.compile(pattern) |
| Échapper les saisies utilisateur | re.escape(text) |
Chapitres associés
- Python Strings — méthodes de string qui complètent les regex pour des tâches textuelles plus simples.
- Modify Strings — méthodes intégrées comme
replace()etsplit()qui évitent les regex lorsque les motifs sont fixes. - Python File Handling — lecture de fichiers ligne par ligne pour appliquer les regex à grande échelle.
- Python Try/Except — gestion des erreurs qui surviennent lorsque les motifs regex sont compilés à partir de saisies utilisateur.
- Python Functions — encapsuler la logique regex dans des fonctions réutilisables.