# regex-con-python-desde-cero
Regex con Python desde Cero
Volver a la portada del cursoPatrones de Repetición
Sección 2 · Patrones y Sintaxis Básica
Patrones de Repetición
En la bandeja de PyShop los teléfonos llegan en cuatro formatos: 8116155940, (55) 3010-3105, 55 1234 5678 y +52 55 1234 5678. Un patrón literal no sirve para ninguno de ellos y escribir uno por formato no escala. Los cuantificadores resuelven el problema: dicen cuántas veces puede repetirse lo que tienen a la izquierda. Con ellos un solo patrón encuentra los 369 teléfonos de la bandeja y deja cada uno en 10 dígitos.
1. Los cuantificadores
| Cuantificador | Repeticiones | En «a1234» |
|---|---|---|
+ | una o más | \d+ → ['1234'] |
* | cero o más | \d* → ['', '1234', ''] |
? | cero o una | colou?r acepta «color» y «colour» |
{m} | exactamente m | \d{4} → ['1234'] |
{m,} | m o más | \d{2,} |
{m,n} | entre m y n | \d{2,3} → ['123'] |
El cuantificador se aplica solo al elemento inmediato: en ab+ se repite la b; para repetir «ab» se agrupa, (?:ab)+. La fila de \d* merece atención: como acepta cero dígitos, coincide con la cadena vacía antes de la «a» y al final del texto. Un patrón que puede coincidir con nada termina devolviendo cadenas vacías; casi siempre se quería +.
2. Codicioso y perezoso
Por defecto los cuantificadores son codiciosos: toman todo lo que pueden y luego retroceden lo justo para que el resto del patrón coincida. En el texto <b>Pedido</b> y <b>Factura</b>, el patrón <.+> no encuentra cuatro etiquetas sino una sola coincidencia: la cadena completa, del primer < al último >.
Agregar ? al cuantificador lo vuelve perezoso: toma lo mínimo y crece solo si hace falta. <.+?> devuelve las 4 etiquetas. Una tercera opción suele ser mejor que ambas: la clase negada <[^>]+> dice exactamente lo que se busca, «caracteres que no cierran la etiqueta», también da 4 y no depende del retroceso.
import re
html = "<b>Pedido</b> y <b>Factura</b>"
print(len(re.findall(r"<.+>", html))) # 1: toda la cadena
print(re.findall(r"<.+?>", html)) # ['<b>', '</b>', '<b>', '</b>']
print(len(re.findall(r"<[^>]+>", html))) # 4
3. Teléfonos y montos de la bandeja
El patrón de teléfono combina ? para las partes opcionales y {m} para los bloques de dígitos. Los tres grupos guardan la lada y los dos bloques; unirlos da el número normalizado:
TEL = re.compile(r"(?:\+52 )?\(?(\d{2})\)?[ -]?(\d{4})[ -]?(\d{4})")
tickets = generar_tickets()
tels = [m for t in tickets for m in TEL.finditer(t)]
print(len(tels)) # 369
print(["".join(m.groups()) for m in tels[:3]])
# ['8116155940', '8182278248', '5530103105']
En la bandeja aparecen 102 teléfonos pegados, 100 con paréntesis, 88 separados por espacios y 79 con +52; el mismo patrón los cubre todos. Con los montos pasa lo mismo: Pagué (\$?[\d,]+(?:\.\d{2})?(?: MXN)?) acepta los tres formatos de la bandeja (con signo de pesos, con comas de miles o con «MXN» al final). Encuentra 488 montos que, una vez limpios con re.sub y convertidos a float, suman 1,211,689.39 MXN, con media de 2,482.97 y máximo de 4,984.50.
4. Backtracking catastrófico
El motor de re prueba caminos y, cuando uno falla, retrocede para probar otro. Con cuantificadores anidados el número de caminos explota. El patrón (a+)+$ sobre una cadena de letras «a» seguida de «!» nunca puede coincidir, pero antes de rendirse prueba todas las formas de repartir las «a» entre el grupo interno y el externo. En la máquina donde se preparó el curso, el tiempo se multiplicó por unas 4 cada vez que se agregaron 2 letras: con 24 letras ya tardaba medio segundo, y con 40 tardaría horas. Un texto de entrada malicioso puede bloquear así un servidor; el ataque se conoce como ReDoS.
La solución más simple es no anidar: a+$ describe lo mismo y responde en microsegundos. Desde Python 3.11, re trae además dos herramientas para cortar el retroceso:
- Grupo atómico
(?>…): una vez que el grupo coincide, el motor no vuelve a entrar en él para probar otras repeticiones.(?>a+)+$también responde en microsegundos. - Cuantificadores posesivos
*+,++,?+y{m,n}+: codiciosos que nunca devuelven lo tomado.\d+5coincide con «12345» porque\d+devuelve el 5;\d++5no coincide, porque\d++se queda con los cinco dígitos.
Trampas comunes
*donde iba+. Produce coincidencias vacías y conteos inflados..+codicioso entre delimitadores. Se come varios campos; usa.+?o, mejor, una clase negada.- Olvidar que
{2,3}también es codicioso. En «a1234» toma 123, no 12. - Repetir un grupo que ya repite.
(\d+)*o(a|aa)+son candidatos a backtracking catastrófico. - Medir con segundos. El tiempo cambia entre máquinas; lo que importa es la proporción de crecimiento.
Cierre
En el notebook grupos-alternancia-repeticion se normalizan los 369 teléfonos y los 488 montos y se mide el crecimiento de (a+)+$ en tu propia máquina. Cuando un patrón tenga un cuantificador dentro de otro, pregúntate si hay más de una forma de repartir el texto entre ellos; si la hay, reescríbelo sin anidar o usa un grupo atómico.