Guides1 chasse jouable incluse

Regex en JavaScript et en Python : les différences qui piègent

Un même motif peut se comporter différemment en JavaScript et en Python. Voici les différences qui causent de vrais bugs : re.match contre test, $ et le saut de ligne final, \d en Unicode, findall et les groupes, replace contre sub, la largeur du lookbehind, la syntaxe des groupes nommés et les marqueurs en ligne. Chaque affirmation vérifiée avec Node 23 et Python 3.12.

Logo de Regex Hunter

· 6 min de lecture

/(?<=\$)\d+(\.\d\d)?/

à attraper

  • $5
  • cost $12.50
  • $0.99
  • pay $100

à épargner

  • 5
  • 5$
  • USD 12
  • $.50

L'essentiel de la syntaxe des regex est commun à JavaScript et à Python : classes, quantificateurs, groupes, ancres, lookarounds. C'est justement ce qui rend les différences dangereuses. Un motif copié de l'un vers l'autre compile généralement sans broncher, puis se comporte légèrement autrement.

Voici les différences qui causent de vrais bugs, à peu près dans l'ordre de fréquence où elles frappent. Chaque comportement ci-dessous a été vérifié avec Node 23 et Python 3.12. Les exemples interactifs de cette page tournent dans votre navigateur : ils montrent donc le comportement de JavaScript.

01re.match ne regarde que le début

En JavaScript, test() et match() cherchent dans toute la chaîne. Python a trois fonctions, et celle qui s'appelle match ne trouve une correspondance qu'au début :

import re

re.match(r"\d", "a1")      # None: the string doesn't start with a digit
re.search(r"\d", "a1")     # matches "1"
re.fullmatch(r"\d", "1")   # the whole string must match
/\d/.test("a1");  // true: test() searches

Un test() JavaScript équivaut donc à un search() Python. Et pour valider une saisie en Python, re.fullmatch est généralement ce qu'il vous faut : aucune ancre à oublier.

02$ et le saut de ligne final

En Python, $ correspond tout à la fin et juste avant un saut de ligne final. En JavaScript, $ (sans le marqueur m) ne correspond que tout à la fin :

re.match(r"^\d{5}$", "90210\n")   # matches in Python
/^\d{5}$/.test("90210\n");        // false in JavaScript

En Python, re.fullmatch ou \Z comblent l'écart. L'article sur les ancres et le mode multiligne détaille ce point.

03En Python, \d et \w sont Unicode

En JavaScript, \d vaut toujours [0-9], même avec le marqueur u. En Python 3, sur un motif de type str, \d correspond à n'importe quel chiffre Unicode et \w à n'importe quelle lettre, quel que soit l'alphabet :

re.fullmatch(r"\d+", "٣٤٥")            # matches in Python (Arabic-Indic digits)
re.fullmatch(r"\d+", "٣٤٥", re.ASCII)  # None
/^\d+$/.test("٣٤٥");   // false
/^\d+$/u.test("٣٤٥");  // still false

Si les chiffres finissent dans un int() ou dans une base de données, utilisez [0-9] ou re.ASCII en Python.

04findall renvoie les groupes, pas les correspondances

Si un motif contient des groupes de capture, findall en Python renvoie les groupes, pas les correspondances entières :

re.findall(r"\w=\d", "a=1 b=2")      # ['a=1', 'b=2']
re.findall(r"(\w)=(\d)", "a=1 b=2")  # [('a', '1'), ('b', '2')]

En JavaScript, match avec le marqueur g renvoie toujours les correspondances entières, quels que soient les groupes du motif :

"a=1 b=2".match(/(\w)=(\d)/g);  // ["a=1", "b=2"]

Ajouter un groupe « juste pour regrouper » à un motif Python change en silence ce que renvoie findall. Utilisez un groupe non capturant (?:…), ou finditer et m.group(0).

05replace remplace une fois, sub remplace tout

En JavaScript, replace remplace la première correspondance, sauf si la regex a le marqueur g. En Python, re.sub les remplace toutes, sauf si vous passez count :

"a-b-c".replace("-", "+");      // "a+b-c"
"a-b-c".replace(/-/g, "+");     // "a+b+c"
"a-b-c".replaceAll("-", "+");   // "a+b+c"
re.sub(r"-", "+", "a-b-c")           # "a+b+c"
re.sub(r"-", "+", "a-b-c", count=1)  # "a+b-c"

La syntaxe de remplacement diffère aussi : $1 et $<name> en JavaScript, \1, \g<1> et \g<name> en Python.

06La largeur du lookbehind

JavaScript accepte n'importe quel lookbehind (assertion arrière), y compris de longueur variable :

Correspondances4/4 comme prévu
/(?<=\$)\d+(\.\d\d)?/

Doit correspondre2/2

  • $5
  • cost $12.50

Ne doit pas2/2

  • 5
  • USD 12
Essayez-le dans le bac à sable
Un lookbehind de largeur fixe comme (?<=\$) fonctionne dans les deux langages.
Correspondances3/3 comme prévu
/(?<=USD ?)\d+/

Doit correspondre2/2

  • USD12
  • USD 12

Ne doit pas1/1

  • EUR 12
Essayez-le dans le bac à sable
L'espace facultative rend ce lookbehind de largeur variable. JavaScript l'accepte.

Le module re de Python rejette le second avec look-behind requires fixed-width pattern. Réécrivez-le en alternatives de largeur fixe, (?:(?<=USD)|(?<=USD ))\d+, ou utilisez le module tiers regex.

07Les groupes nommés ne s'écrivent pas pareil

JavaScript Python
Définir (?<year>\d{4}) (?P<year>\d{4})
Y faire référence \k<year> (?P=year)
Dans un remplacement $<year> \g<year>
Le lire m.groups.year m.group("year")

Le re de Python 3.12 rejette la forme JavaScript avec unknown extension ?<y. Un motif à groupes nommés ne passe pas de l'un à l'autre sans retouches.

08Les marqueurs en ligne

Python accepte des marqueurs à l'intérieur du motif, au début : (?i)abc. Un marqueur global placé ailleurs est une erreur. Les deux langages prennent en charge les marqueurs à portée limitée sur un groupe, (?i:a)b, qui rend seul a insensible à la casse. En JavaScript, c'est nouveau (ES2025, dans Node 23 et les versions actuelles de Chrome et Firefox) : vérifiez vos cibles. JavaScript n'a pas du tout de (?i) en tête de motif ; placez les marqueurs après la barre oblique.

09Ce qu'un seul des deux possède

  • Python uniquement : les groupes atomiques (?>…) et les quantificateurs possessifs a++ (3.11+), \A et \Z, les groupes conditionnels (?(1)…).
  • JavaScript uniquement : les échappements de propriétés Unicode \p{L} avec le marqueur u (Python a besoin du module regex), le marqueur sticky y, et les indices de correspondance avec d.

10L'échappement dans les chaînes

Les motifs Python vivent dans des chaînes : écrivez-les donc en chaînes brutes, r"\d+", sinon chaque barre oblique inverse doit être doublée. Les littéraux regex de JavaScript, /\d+/, ne demandent aucun échappement supplémentaire. Mais new RegExp("\\d+") construit la regex à partir d'une chaîne, et il faut alors doubler la barre oblique inverse, exactement comme dans une chaîne Python non brute.

11À vous de jouer

Le plateau interactif tourne en JavaScript, donc un lookbehind comme (?<=\$)\d fonctionne ici. Le par en demande moins :

Chasse jouablepar 4

Attrapez les lignes qui ont un nombre juste après un signe $.

Cibles · 0/4 attrapées

  • $5cible
  • cost $12.50cible
  • pay $100 nowcible
  • $0cible

Innocents · erreurs : 0

  • 5innocent
  • 5$innocent
  • USD 12innocent
  • $ 5innocent
  • $.50innocent

Tapez une regex — le plateau se met à jour en direct.

Essayez-le dans le bac à sable
Ça vous plaît ? Une nouvelle chasse chaque jour.Jouer la chasse du jour

12Récapitulatif

  • En JS, test() cherche partout. En Python, re.match ne vérifie que le début. Utilisez re.search ou re.fullmatch.
  • Le $ de Python tolère un saut de ligne final. Utilisez re.fullmatch ou \Z.
  • En Python, \d et \w sont Unicode. Utilisez [0-9] ou re.ASCII.
  • findall en Python renvoie les groupes quand le motif en contient.
  • En JS, replace ne change que la première correspondance, sauf avec g ou replaceAll. En Python, sub les change toutes.
  • Python exige un lookbehind de largeur fixe.
  • Groupes nommés : (?<n>…) en JS, (?P<n>…) en Python.

13Continuez à vous entraîner