Saltar al contenido

# regex-con-python-desde-cero

Regex con Python desde Cero

Volver a la portada del curso
Notebook

Notebooks de Práctica Avanzada

Sección 3 · Regex Avanzado en Python

Notebooks de Práctica Avanzada

El notebook practica-avanzada lleva a la bandeja de PyShop las herramientas de la sección: lookarounds, re.VERBOSE, sub con función, finditer con posiciones, validadores con fullmatch y patrones compilados. Es autocontenido: la primera celda de código define generar_tickets y generar_log, así que corre de principio a fin sin descargar nada y siempre produce las mismas cifras. Esta guía explica qué hace cada bloque, qué resultado esperar y en qué fijarse.

1. Preparación

Solo se usan la biblioteca estándar (re, collections.Counter, time) y Python 3.11 o superior; se probó con Python 3.13, donde el error de patrón se llama re.PatternError (en versiones anteriores solo existe re.error, y el código usa este nombre para funcionar en ambas). La celda de datos genera 600 tickets y 2000 líneas de log con semillas fijas. Ejecuta las celdas en orden: varias reutilizan patrones definidos antes.

2. Lookarounds: mirar sin consumir

Un lookahead (?=…) o un lookbehind (?<=…) exige que algo esté a la derecha o a la izquierda sin incluirlo en la coincidencia; las versiones negativas son (?!…) y (?<!…). En la bandeja hay 334 montos escritos con signo de pesos y 154 con «MXN» al final, 488 en total. El notebook los separa con dos patrones:

import re

bandeja = "\n\n".join(generar_tickets())
con_signo = re.findall(r"(?<=\$)[\d,]+(?:\.\d{2})?", bandeja)
con_mxn = re.findall(r"\d+(?:\.\d{2})?(?= MXN)", bandeja)
print(len(con_signo), len(con_mxn))   # 334 154

Después viene la trampa de la sección: \b\d+(?:\.\d{2})?\b(?! MXN) sobre «500.00 MXN y 300» devuelve ['500', '300']. El lookahead negativo falla en «500.00», pero el motor retrocede, prueba «500» y ahí sí se cumple, porque lo que sigue es «.00». El patrón correcto, \b\d+(?:\.\d+)?(?![\d.]| MXN), impide detenerse a mitad del número y devuelve ['300']. Un lookaround negativo casi siempre necesita cerrar el número por ambos lados.

La última celda del bloque intenta compilar (?<=\$\d+)x y captura el error: el lookbehind de re debe tener ancho fijo, y Python 3.13 lanza re.PatternError (alias de re.error).

3. Validador de contraseñas

Varios lookaheads al inicio comprueban condiciones independientes sobre la misma cadena. El validador exige al menos una mayúscula, una minúscula, un dígito, un símbolo y 10 caracteres:

FUERTE = r"(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^\w\s]).{10,}"

def valida(pw):
    return re.fullmatch(FUERTE, pw) is not None

for pw in ["pyshop2026", "PyShop2026", "PyShop2026!", "Py!2026a"]:
    print(pw, valida(pw))
# False, False, True, False

«Py!2026a» cumple los cuatro lookaheads pero tiene 8 caracteres. Al imprimir cuál condición falla en cada caso se ve por qué conviene un mensaje por regla y no solo un False.

4. re.VERBOSE: patrones que se leen

Con re.VERBOSE los espacios del patrón se ignoran y # abre un comentario. El correo se escribe en tres líneas comentadas, usuario, arroba y dominio, y sobre la bandeja encuentra 600 correos, 352 distintos, de los cuales 138 son internos de pyshop.mx. Si necesitas un espacio literal en modo verbose, escríbelo como \ o dentro de una clase [ ].

5. sub con función y subn

La función de reemplazo recibe cada Match. El notebook la usa para conservar la inicial del usuario (v***@ejemplo.org) y con subn cuenta los reemplazos sobre la bandeja: 600 correos y 369 teléfonos. Ese conteo es la prueba de que la anonimización tocó todo lo esperado.

6. finditer con posiciones

Cada Match trae span(). En el ticket T-0003 el pedido ps-20458 ocupa las posiciones (84, 92). Con esas posiciones el notebook marca el pedido entre corchetes dentro del texto sin alterar el resto, recorriendo las coincidencias de derecha a izquierda para que los índices no se desplacen.

7. Validadores con fullmatch

Validar es comprobar el texto completo. Se construyen validadores para pedido (PS-\d{5}), teléfono normalizado (\d{10}) y fecha ISO con mes de 01 a 12 y día de 01 a 31. De las fechas de prueba 2026-02-24, 2026-13-01, 2026-02-30 y 26-02-24 pasan dos: la regex rechaza el mes 13 y el año corto, pero acepta el 30 de febrero. La regex valida la forma; el calendario lo valida datetime.date.fromisoformat.

8. Compilar

El bloque final repite 200 veces la búsqueda del pedido en los 600 tickets, con re.search y una cadena y con el patrón compilado. Los segundos dependen de la máquina, así que el notebook imprime los dos tiempos y su cociente medido en tu equipo; lo esperable es que el compilado gane por poco, porque re ya guarda en caché los patrones recientes.

Ejercicios

Al final hay cinco ejercicios con assert: extraer montos seguidos de «MXN», anonimizar teléfonos dejando los últimos 4 dígitos, validar pedidos con fullmatch, escribir el patrón del log con re.VERBOSE y comentarios, y detectar qué regla incumple cada contraseña. Si un assert falla, imprime la coincidencia y su span() antes de cambiar el patrón.

Trampas que el notebook expone

  • Un lookahead negativo sin cerrar el número deja pasar fragmentos por backtracking.
  • Lookbehind de ancho variable: re lo rechaza al compilar.
  • En modo verbose, un espacio sin escapar desaparece del patrón.
  • fullmatch valida la forma, no el significado de la fecha.

Recursos