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.

· 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:
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 posesivosa++(3.11+),\Ay\Z, grupos condicionales(?(1)…). - Solo JavaScript: escapes de propiedades Unicode
\p{L}con el modificadoru(Python necesita el móduloregex), el modificadory(sticky) y los índices de coincidencia cond.
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:
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.
12Lista de comprobación
test()de JS busca en toda la cadena.re.matchde Python solo mira el principio. Usare.searchore.fullmatch.- El
$de Python admite un salto de línea final. Usare.fullmatcho\Z. \dy\wde Python son Unicode. Usa[0-9]ore.ASCII.findallde Python devuelve grupos cuando el patrón los tiene.replacede JS cambia la primera coincidencia salvo que usesgoreplaceAll.subde Python las cambia todas.- Python necesita lookbehind de ancho fijo.
- Grupos con nombre:
(?<n>…)en JS,(?P<n>…)en Python.
13Sigue practicando
- La sección de variantes de la guía compara motores de regex lado a lado.
- Los artículos sobre lookahead y lookbehind y grupos con nombre profundizan en dos de estos puntos.
- Prueba la caza de hoy o repasa tableros anteriores en el archivo.