Développement Web

Assertions regex : lookahead, lookbehind et limites de mots

11 min de lecture

Capturer, c’est bien ; vérifier un contexte sans le consommer, c’est ce qui sépare un motif fragile d’un motif robuste. Pour Téranga Livraison, on veut souvent dire « ce nombre, seulement s’il est suivi de FCFA » ou « cette référence, seulement si c’est un mot entier ». Les assertions — limites de mots, lookahead et lookbehind — répondent exactement à ce besoin : elles posent une condition sur ce qui entoure une position, sans l’inclure dans le résultat.

📍 Article principal du parcours : Maîtriser les expressions régulières : le guide complet
Ce tutoriel fait partie du parcours « Expressions régulières ». Pour l’ordre conseillé, commencez par le guide.

🎯 Ce que vous allez apprendre

  • Utiliser les limites de mots \b et leur négation \B pour isoler un mot entier.
  • Poser une condition « suivi de » avec le lookahead positif (?=...) et « non suivi de » avec le négatif (?!...).
  • Faire de même en amont avec le lookbehind (?<=...) et (?<!...), et connaître la limite « longueur fixe » de Python.
  • Combiner plusieurs lookaheads pour valider qu’une chaîne respecte plusieurs règles à la fois.

🛠️ Ce que vous allez construire

Un validateur de code de suivi pour les colis de Téranga Livraison : un code valide doit contenir au moins une lettre majuscule et au moins un chiffre, et faire exactement six caractères. Vous verrez aussi comment extraire un montant en FCFA sans capturer le mot « FCFA » lui-même.

Prérequis

  • Avoir suivi Groupes, captures et alternation (les assertions s’appuient sur les groupes).
  • Un bac à sable comme regex101.com, idéalement avec le panneau d’explication activé.
  • Niveau : intermédiaire débutant. ⏱️ ~35 minutes.

Étape 1 — Limites de mots \b et \B

On a croisé \b dans le premier tutoriel ; détaillons-la. Une limite de mot est une position — pas un caractère — entre un caractère de mot (\w, c’est-à-dire lettre, chiffre ou souligné) et un caractère qui n’en est pas un (espace, ponctuation, début ou fin de chaîne). Elle ne consomme rien : elle affirme seulement « ici, on change de nature ».

\bCMD\b      → "CMD" entouré de frontières de mot
Reconnaît :  "la CMD-2026 arrive"   (CMD isolé)
Ignore     :  "SCMDX"               (CMD collé à d'autres lettres)

Son opposé \B affirme l’absence de frontière, donc « au milieu d’un mot ». \Bcat\B trouve « cat » dans « application » (entre le « i » et le « i »), mais pas dans « le cat noir » où « cat » est un mot isolé. En pratique, \b est l’outil de prédilection pour le rechercher-remplacer : remplacer le mot « commande » sans toucher à « télécommande » exige \bcommande\b. Oublier ces frontières est l’erreur n°1 du rechercher-remplacer regex.

Point d’étape — Testez \bGET\b sur « GET /x puis TARGET ». Seul le premier « GET » doit s’allumer ; le « GET » de « TARGET » est ignoré car il n’est pas en limite de mot à gauche. Retirez les \b : les deux s’allument.

Étape 2 — Le lookahead positif (?=...)

Un lookahead positif regarde en avant : (?=...) affirme « à partir d’ici, ce qui suit correspond à ce motif », mais sans avancer le curseur ni inclure ce qui suit dans la correspondance. C’est une condition, pas une capture. Cas concret : retrouver un montant uniquement quand il est suivi de l’unité « FCFA ».

\d+(?= FCFA)

Sur "Total 12500 FCFA payé 0 fois" :
  reconnaît  12500   (suivi de " FCFA")
  ignore     0       (suivi de " fois")

Le moteur reconnaît \d+, puis vérifie que la position suivante est bien « espace + FCFA » — sans inclure « FCFA » dans le résultat. La correspondance se limite donc à « 12500 ». C’est précieux quand on veut extraire une valeur sans s’embarrasser de son étiquette. On peut empiler plusieurs lookaheads : le moteur les évalue tous à la même position, comme une liste de conditions à satisfaire simultanément.

Étape 3 — Le lookahead négatif (?!...)

Le lookahead négatif (?!...) est la condition inverse : « à partir d’ici, ce qui suit ne correspond pas à ce motif ». Pour Téranga Livraison, isolons les nombres qui ne sont pas des montants — par exemple un identifiant qui ne doit jamais être suivi de « FCFA ».

\d+(?! FCFA)

Sur "ref 428 montant 12500 FCFA" :
  reconnaît  428      (non suivi de " FCFA")
  ignore     12500    (suivi de " FCFA", donc exclu)

Subtilité fréquente : sur « 12500 FCFA », vous verrez peut-être le moteur reconnaître « 1250 » (sans le dernier 5). Pourquoi ? Parce que \d+ est gourmand mais peut reculer : si « 12500 » échoue (suivi de FCFA), le moteur essaie « 1250 », dont le caractère suivant est « 0 » — pas « FCFA » — donc la condition passe. Pour exiger un nombre entier non suivi de FCFA, on ajoute une limite : \b\d+\b(?! FCFA). Ce genre de surprise est exactement pourquoi on teste toujours ses assertions sur des cas réels.

Point d’étape — Testez \b\d+\b(?! FCFA) sur « 428 et 12500 FCFA ». Seul « 428 » doit rester. Si « 1250 » apparaît, c’est que les limites \b manquent.

Étape 4 — Le lookbehind (?<=...) et sa limite Python

Le lookbehind fait la même chose en regardant en arrière. (?<=...) affirme « ce qui précède correspond à ce motif » ; (?<!...) affirme l’inverse. Pour extraire un montant placé après l’étiquette « montant= » sans capturer l’étiquette :

(?<=montant=)\d+

Sur "ref=428 montant=12500" :
  reconnaît  12500   (précédé de "montant=", non inclus)

Ici survient une divergence importante entre langages. JavaScript autorise un lookbehind de longueur variable depuis ES2018 : (?<=ref-\d+ ) est accepté. Le module re de Python, lui, n’autorise qu’un lookbehind de longueur fixe : (?<=montant=) passe (longueur constante), mais (?<=ref-\d+ ) est refusé car \d+ a une longueur variable, et même une alternation de longueurs différentes comme (?<=ab|abc) est rejetée. Si vous avez besoin d’un lookbehind variable en Python, la bibliothèque tierce regex (sur PyPI) le permet. Gardez ce point en tête : un motif qui marche en JavaScript peut être refusé tel quel en Python.

Point d’étape — Dans regex101, saveur « ECMAScript », testez (?<=montant=)\d+ sur « montant=12500 » : « 12500 » s’allume sans l’étiquette. Basculez en saveur « Python » et essayez (?<=ref-\d+ )\w+ : vous obtenez l’erreur « look-behind requires fixed-width pattern ». C’est la limite en action.

Étape 5 — Combiner : valider un code de suivi

Voici l’application qui rassemble tout. Un code de suivi de colis chez Téranga Livraison fait exactement six caractères alphanumériques, et doit contenir au moins une lettre majuscule et au moins un chiffre. C’est l’archétype de la validation par lookaheads empilés.

^(?=.*[A-Z])(?=.*\d)[A-Z0-9]{6}$

Reconnaît :  "AB3K90"   (a des majuscules ET un chiffre, 6 caractères)
Ignore     :  "ABCDEF"   (pas de chiffre)
Ignore     :  "123456"   (pas de majuscule)
Ignore     :  "AB3K9"    (5 caractères)

Lisons-le pas à pas. ^ ancre au début. (?=.*[A-Z]) est un lookahead : « quelque part devant, il y a une majuscule » — sans avancer. (?=.*\d) exige de même un chiffre. Comme les lookaheads ne consomment rien, le curseur est toujours au début après ces deux vérifications. Vient alors la vraie consommation : [A-Z0-9]{6} lit six caractères alphanumériques majuscules, et $ exige la fin. Les deux lookaheads agissent comme des conditions d’entrée ; le corps du motif fait le travail. Ce patron « ^(?=règle1)(?=règle2)corps$ » est le couteau suisse de la validation de mots de passe, de références ou de codes — on l’écrit une fois et on l’adapte à l’infini.

Point d’étape — Collez le motif et la liste d’exemples ci-dessus. Seul « AB3K90 » doit correspondre. Si « ABCDEF » passe, c’est que le lookahead (?=.*\d) manque ou est mal placé (il doit être avant le corps, pas après).

Étape 6 — Une astuce classique : séparer les milliers

Voici l’un des usages les plus élégants du lookahead, et il tombe à pic pour afficher proprement des montants en FCFA. On veut transformer « 12500 » en « 12 500 », c’est-à-dire insérer une espace tous les trois chiffres en partant de la droite. Le problème, vu naïvement, est pénible ; vu comme un lookahead, il tient en une ligne.

Motif        :  \B(?=(\d{3})+(?!\d))
Remplacement :  une espace

"12500"    → "12 500"
"1250000"  → "1 250 000"
"428"      → "428"  (inchangé)

Décodons cette petite merveille. \B vise une position entre deux chiffres (jamais au bord du nombre). À chacune de ces positions, le lookahead (?=(\d{3})+(?!\d)) vérifie qu’« en avant, il reste un nombre de chiffres multiple de trois, puis plus aucun chiffre ». Si c’est le cas, on est exactement sur une frontière de millier, et le remplacement y glisse une espace. Sur « 12500 », seule la position après « 12 » satisfait la condition (il reste « 500 », soit trois chiffres puis la fin) ; on obtient « 12 500 ». Sur « 1250000 », deux positions conviennent, d’où « 1 250 000 ». Aucun groupe n’est consommé : on ne fait qu’insérer aux bons endroits. C’est le genre de motif qu’on garde précieusement dans sa boîte à outils — il marche à l’identique en JavaScript et en Python, et rend lisibles tous les montants d’un tableau de bord.

Point d’étape — Dans regex101, mode « Substitution », motif \B(?=(\d{3})+(?!\d)), remplacement « une espace », sur « Total 1250000 FCFA ». Vous devez lire « Total 1 250 000 FCFA ». Si des espaces apparaissent au mauvais endroit, vérifiez le (?!\d) final qui borne le dernier groupe.

🐞 Pièges fréquents

Symptôme Cause probable Correctif
« look-behind requires fixed-width pattern » en Python Lookbehind de longueur variable interdit par re Rendre la longueur fixe, ou utiliser la lib regex de PyPI
Le lookahead négatif laisse passer une version tronquée du nombre Le moteur recule (\d+ gourmand) jusqu’à satisfaire la condition Ancrer avec \b : \b\d+\b(?!...)
« invalid group reference » ou assertion ignorée Confusion entre (?=...) (lookahead) et (?:...) (groupe) Vérifier le caractère après ? : =, !, <=, <!
\b ne marche pas comme prévu sur de l’Unicode Définition de « mot » limitée à l’ASCII selon le moteur/flag Activer le mode Unicode (flag u en JS) si besoin

🌍 Adaptation au contexte ouest-africain

La validation par lookaheads est exactement ce qu’il faut pour les formulaires d’inscription d’une plateforme locale : exiger qu’un identifiant de boutique contienne une lettre et un chiffre, qu’un code promo respecte un format précis, ou qu’un numéro mobile money soit complet avant de l’enregistrer. Ces vérifications tournent côté navigateur, sans appel serveur — un atout quand la connexion est lente ou intermittente. Une seule expression bien pensée remplace une cascade de if et fonctionne aussi bien dans un champ HTML (attribut pattern) que dans votre code JavaScript ou Python.

✅ Récapitulatif

Les assertions vérifient un contexte sans le consommer. \b/\B marquent (ou nient) une frontière de mot. Le lookahead (?=...)/(?!...) impose une condition sur ce qui suit ; le lookbehind (?<=...)/(?<!...) sur ce qui précède — avec la réserve que Python exige un lookbehind de longueur fixe. En empilant des lookaheads, on valide plusieurs règles d’un coup : votre validateur de code de suivi ^(?=.*[A-Z])(?=.*\d)[A-Z0-9]{6}$ en est la preuve.

🧾 Aide-mémoire

Élément Signification
\b / \B Limite de mot / absence de limite
(?=...) Lookahead positif : suivi de…
(?!...) Lookahead négatif : non suivi de…
(?<=...) Lookbehind positif : précédé de…
(?<!...) Lookbehind négatif : non précédé de…
^...$ Ancres début / fin (ligne ou texte)

💪 À vous de jouer

Écrivez un motif qui extrait le code de statut HTTP d’une ligne de log uniquement s’il s’agit d’une erreur 4xx ou 5xx (statut commençant par 4 ou 5), en utilisant un lookbehind pour le guillemet fermant qui précède le statut.

Voir une solution

(?<=" )[45]\d{2} appliqué à la fin de la ligne ... HTTP/1.1" 404. Le lookbehind (?<=" ) (guillemet + espace, longueur fixe — donc valide même en Python) vérifie le contexte, puis [45]\d{2} consomme un code 4xx ou 5xx. Un statut 200 est ignoré car il ne commence ni par 4 ni par 5.

Tutoriels frères

Pour aller plus loin

FAQ

Q : Une assertion peut-elle capturer du texte ?
R : Une assertion elle-même ne consomme rien, mais vous pouvez placer un groupe capturant à l’intérieur : (?=(\d+)) capture le nombre vu en avant sans l’inclure dans la correspondance principale. C’est une technique avancée utile pour les correspondances qui se chevauchent.

Q : Pourquoi mon lookahead ralentit-il tout ?
R : Un lookahead du type (?=.*X) rescanne potentiellement le reste de la chaîne à chaque position. Sur de très longues lignes, multiplier ces motifs « .* dans un lookahead » peut coûter cher. Limitez la portée quand c’est possible (par exemple en ancrant) ou validez ligne par ligne.

Q : Le lookbehind est-il supporté partout ?
R : Aujourd’hui oui dans les environnements modernes (navigateurs à jour, Node, Python, PCRE2, .NET). La nuance reste la longueur : variable en JavaScript et .NET, fixe en Python et PCRE2. Vérifiez toujours dans la saveur cible.

Mots-clés : assertions regex, lookahead, lookbehind, limite de mot, \b, (?=), (?!), (?<=), validation regex, longueur fixe Python.

Partager