Guías1 caza jugable incluida

Regex en JavaScript y en Python: las diferencias que dan problemas

El mismo patrón puede comportarse distinto en JavaScript y en Python. Las diferencias que causan bugs reales: re.match frente a test, $ y el salto de línea final, \d en Unicode, findall y los grupos, replace frente a sub, el ancho del lookbehind, la sintaxis de los grupos con nombre y los modificadores en línea. Todo comprobado en Node 23 y Python 3.12.

Logo de Regex Hunter

· 5 min de lectura

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

atrapa

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

deja libre

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

Casi toda la sintaxis de las regex es común a JavaScript y Python: clases, cuantificadores, grupos, anclas, lookarounds. Precisamente por eso las diferencias son peligrosas. Un patrón copiado de un lenguaje al otro suele compilar, y luego se comporta de forma ligeramente distinta.

Estas son las diferencias que causan bugs reales, más o menos en orden de frecuencia. Todo lo que sigue se comprobó en Node 23 y Python 3.12. Los ejemplos interactivos de esta página se ejecutan en tu navegador, así que muestran el comportamiento de JavaScript.

01re.match solo mira el principio

test() y match() de JavaScript buscan en toda la cadena. Python tiene tres funciones, y la que se llama match solo encuentra coincidencias al principio:

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

Así que un test() de JavaScript equivale a un search() de Python. Y para validar en Python, re.fullmatch suele ser lo que quieres: no hay anclas que olvidar.

02$ y el salto de línea final

En Python, $ coincide al final del todo y justo antes de un salto de línea final. El $ de JavaScript (sin el modificador m) solo coincide al final del todo:

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

En Python, re.fullmatch o \Z resuelven el problema. El artículo sobre anclas y modo multilínea lo explica en detalle.

03\d y \w en Python son Unicode

En JavaScript, \d siempre es [0-9], incluso con el modificador u. En Python 3, con un patrón str, \d coincide con cualquier dígito Unicode y \w con cualquier letra de cualquier alfabeto:

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 los dígitos van a acabar en int() o en una base de datos, usa [0-9] o re.ASCII en Python.

04findall devuelve grupos, no coincidencias

Si un patrón tiene grupos de captura, findall de Python devuelve los grupos, no las coincidencias completas:

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')]

match de JavaScript con el modificador g siempre devuelve coincidencias completas, tenga los grupos que tenga el patrón:

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

Añadir un grupo “solo para agrupar” a un patrón de Python cambia en silencio lo que devuelve findall. Usa un grupo sin captura (?:…), o finditer y m.group(0).

05replace reemplaza una vez, sub las reemplaza todas

replace de JavaScript reemplaza la primera coincidencia salvo que la regex lleve g. re.sub de Python las reemplaza todas salvo que le pases 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 sintaxis del reemplazo también cambia: $1 y $<name> en JavaScript, \1, \g<1> y \g<name> en Python.

06El ancho del lookbehind

JavaScript admite cualquier lookbehind, incluidos los de longitud variable:

Coincidencias4/4 como se esperaba
/(?<=\$)\d+(\.\d\d)?/

Debe coincidir2/2

  • $5
  • cost $12.50

No debe2/2

  • 5
  • USD 12
Pruébalo en la Zona de pruebas
Un lookbehind de ancho fijo como (?<=\$) funciona en los dos lenguajes.
Coincidencias3/3 como se esperaba
/(?<=USD ?)\d+/

Debe coincidir2/2

  • USD12
  • USD 12

No debe1/1

  • EUR 12
Pruébalo en la Zona de pruebas
El espacio opcional hace que este lookbehind sea de ancho variable. JavaScript lo acepta.

El módulo re de Python rechaza el segundo con look-behind requires fixed-width pattern. Reescríbelo como alternativas de ancho fijo, (?:(?<=USD)|(?<=USD ))\d+, o usa el módulo de terceros regex.

07Los grupos con nombre se escriben distinto

JavaScript Python
Definirlo (?<year>\d{4}) (?P<year>\d{4})
Referirse a él \k<year> (?P=year)
En un reemplazo $<year> \g<year>
Leerlo m.groups.year m.group("year")

El re de Python 3.12 rechaza la forma de JavaScript con unknown extension ?<y. Un patrón con grupos con nombre no pasa de un lenguaje al otro sin retoques.

08Modificadores en línea

Python acepta modificadores dentro del patrón, al principio: (?i)abc. Un modificador global en cualquier otro sitio es un error. Los dos lenguajes admiten modificadores acotados a un grupo, (?i:a)b, que hace que solo a no distinga mayúsculas. En JavaScript es una novedad (ES2025, en Node 23 y en las versiones actuales de Chrome y Firefox), así que comprueba tus entornos. JavaScript no tiene (?i) al principio; los modificadores van después de la barra.

09Lo que solo tiene uno de los dos

  • Solo Python: grupos atómicos (?>…) y cuantificadores posesivos a++ (3.11+), \A y \Z, grupos condicionales (?(1)…).
  • Solo JavaScript: escapes de propiedades Unicode \p{L} con el modificador u (Python necesita el módulo regex), el modificador y (sticky) y los índices de coincidencia con d.

10El escapado en las cadenas

Los patrones de Python viven en cadenas, así que escríbelos como cadenas raw, r"\d+", o tendrás que duplicar cada barra invertida. Los literales de regex de JavaScript, /\d+/, no necesitan escapado extra. Pero new RegExp("\\d+") se construye a partir de una cadena y necesita la barra invertida duplicada, igual que una cadena de Python que no sea raw.

11Pruébalo

El tablero interactivo usa JavaScript, así que aquí funciona un lookbehind como (?<=\$)\d. El par pide menos:

Caza jugablepar 4

Atrapa las líneas con un número justo después de un signo $.

Objetivos · 0/4 atrapados

  • $5objetivo
  • cost $12.50objetivo
  • pay $100 nowobjetivo
  • $0objetivo

Inocentes · 0 errores

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

Escribe una regex: el tablero se actualiza mientras escribes.

Pruébalo en la Zona de pruebas
¿Te gustó? Cada día llega una nueva.Jugar la caza de hoy

12Lista de comprobación

  • test() de JS busca en toda la cadena. re.match de Python solo mira el principio. Usa re.search o re.fullmatch.
  • El $ de Python admite un salto de línea final. Usa re.fullmatch o \Z.
  • \d y \w de Python son Unicode. Usa [0-9] o re.ASCII.
  • findall de Python devuelve grupos cuando el patrón los tiene.
  • replace de JS cambia la primera coincidencia salvo que uses g o replaceAll. sub de Python las cambia todas.
  • Python necesita lookbehind de ancho fijo.
  • Grupos con nombre: (?<n>…) en JS, (?P<n>…) en Python.

13Sigue practicando