R

ReflexKR

Documentación de Reflex
Expresiones regulares a la vista
Lección 9

Tiempo 6:40–7:30

Las reglas de la caja fuerte: lookaround

CASE-009

Grep & Co. Agencia de Detectives de Datos

Expediente

Las reglas de la caja fuerte

Cliente

La directora Han Byeol

Pruebas

La vieja caja fuerte de la agencia, seis candidatas a nueva contraseña y el libro de cuentas que hay dentro.

Para cerrar el caso

Comprobar con un solo patrón las reglas de la nueva contraseña (8 caracteres o más, con al menos un dígito, una minúscula y un carácter especial) y poner separadores de miles en las cifras del libro de cuentas.

La directora se agacha frente a la vieja caja fuerte del rincón. «Lo de la caja fuerte es solo para asustarnos. Aun así, hoy cambiamos la contraseña. Y esta vez, cumpliendo las reglas».

La nueva contraseña tiene que cumplir tres reglas, en cualquier orden. La directora las ha anotado en coreano: 8 caracteres o más; al menos un dígito, una minúscula y un carácter especial; y comprobarlo todo con una sola regex.

규칙 1. 8자 이상
규칙 2. 숫자, 소문자, 특수문자가 각각 하나 이상
규칙 3. 이 모든 걸 정규식 하나로 검사한다

후보:
vault2026!
vault2026
VAULT2026!
v!2
longpassword!
safe#2026box

Te viene a la cabeza la frase de la tarjeta: «Si son capaces de comprobarlas todas desde el mismo sitio». La sintaxis de hoy es justo eso.

9-1. Una sintaxis que comprueba posiciones, no caracteres (6 min)

Recuerda ^, $ y \b de la lección 4. No se comían ningún carácter: solo comprobaban una posición, del tipo «¿esto es el principio?» o «¿esto es un límite de palabra?». A este tipo de coincidencia se le llama coincidencia de ancho cero (zero-width).

re.findall(r"\b", "hi yo")      # ['', '', '', '']
re.sub(r"\b", "|", "hi yo")     # '|hi| |yo|'

Lo que encontró \b son cuatro cadenas vacías, es decir, cuatro posiciones. El lookaround de hoy es la sintaxis que convierte esa «comprobación de posición» en la condición que tú quieras.

9-2. Comprobar lo que viene después: (?=...) (8 min)

X(?=Y) busca «una X seguida de Y». La Y solo se comprueba; no forma parte de la coincidencia. Esto se llama lookahead (búsqueda hacia delante).

re.findall(r"\d+(?=원)", "3000원짜리 5개, 합계 15000원")   # ['3000', '15000']

La frase es coreana: «artículo de 3000 원, 5 unidades, total 15000 원» (원 es el won, la moneda de Corea, y 개 significa «unidades»). El 5 de 5개 quedó fuera porque no le sigue 원, y los números encontrados no incluyen el 원. Es perfecto cuando buscas «números que terminan en 원» pero solo quieres sacar el número.

Laboratorio de pruebas

re.search(r"", text)

Valor devuelto

Con solo \d+ también se captura la cantidad 5. Añade un lookahead para que solo queden los números seguidos de 원.

9-3. Lo que no debe venir después: (?!...) (6 min)

X(?!Y) es lo contrario: «una X a la que no sigue Y». Se llama lookahead negativo.

re.findall(r"\b(?!tmp)\w+", "tmp_log report tmpfile final")   # ['report', 'final']
re.findall(r"\d{3}(?!\d)", "12345 678")                       # ['345', '678']
  • La primera línea busca «palabras que no empiezan por tmp»: justo después del límite de palabra, comprueba que no venga tmp.
  • La segunda busca «tres cifras a las que no sigue otra cifra», es decir, las últimas tres cifras de cada bloque de números.

9-4. Varias reglas: lookaheads uno tras otro (8 min)

Las condiciones del tipo «tiene que tener esto, y esto, y esto otro», como las reglas de la contraseña, se escriben poniendo lookaheads en fila.

pw = r"(?=.*\d)(?=.*[a-z])(?=.*[^\w\s]).{8,}"
PiezaQué comprueba
(?=.*\d)Hay un dígito en algún sitio
(?=.*[a-z])Hay una minúscula en algún sitio
(?=.*[^\w\s])Hay un carácter especial (algo que no es letra, dígito, guion bajo ni espacio) en algún sitio
.{8,}Se come de verdad 8 caracteres o más

Los tres lookaheads se comprueban desde el mismo sitio (el principio). Como no se comen ningún carácter, al terminar uno la posición sigue igual y la siguiente condición vuelve a empezar desde el principio. Por eso, ponerlos seguidos equivale a un «Y» (AND).

cands = ["vault2026!", "vault2026", "VAULT2026!", "v!2", "longpassword!", "safe#2026box"]
[c for c in cands if re.fullmatch(pw, c)]   # ['vault2026!', 'safe#2026box']

Laboratorio de pruebas

re.search(r"", text)

Valor devuelto

.{8,} solo mira la longitud. Añade delante las condiciones de dígito, minúscula y carácter especial con lookaheads.

El consejo de Dot

El lookahead solo echa un vistazo hacia delante, sin mover las patas. Como yo, que me siento en el alféizar a mirar fuera pero no salto. Por eso, por muchas veces que mire, sigo en el mismo sitio.

9-5. Lo que había antes: (?<=...) (?<!...) (8 min)

Si das la vuelta a la dirección, tienes el lookbehind (búsqueda hacia atrás). (?<=Y)X es «una X precedida de Y» y (?<!Y)X es «una X no precedida de Y».

re.findall(r"(?<=\$)\d+", "$30 and 40 and $5")      # ['30', '5']
re.findall(r"(?<!\$)\b\d+", "$30 and 40 and $5")    # ['40']

El lookbehind tiene una limitación: en el re de Python, la longitud que se mira hacia atrás tiene que ser fija.

re.compile(r"(?<=a+)b")      # PatternError: look-behind requires fixed-width pattern
re.compile(r"(?<=ab|cd)e")   # correcto: las dos opciones tienen 2 caracteres
re.compile(r"(?<=a|bc)d")    # PatternError: look-behind requires fixed-width pattern

Puede haber varias opciones siempre que todas tengan la misma longitud. Si no, divide el lookbehind en dos o agrúpalos por separado, como en (?:(?<=a)|(?<=bc))d.

Laboratorio de pruebas

re.search(r"", text)

Valor devuelto

\$\d+ también saca el $. Mueve el \$ a un lookbehind para que el valor devuelto sea ['30', '5'].

9-6. El libro de la caja fuerte: separadores de miles (4 min)

En el libro de cuentas de la caja fuerte los números van pegados, como 1234567, y cuesta leerlos. Basta con encontrar las posiciones donde va una coma y meterla ahí. Como se trata de encontrar posiciones, el lookaround es la herramienta ideal.

re.sub(r"(?<=\d)(?=(?:\d{3})+(?!\d))", ",", "1234567 and 89 and 1000")
# '1,234,567 and 89 and 1,000'

Se lee así: «una posición con un dígito delante ((?<=\d)) y, detrás, uno o más grupos de tres dígitos ((?:\d{3})+) tras los cuales se acaban los dígitos ((?!\d))». Esa posición no es un carácter, así que sub no borra nada: solo intercala la coma.

Si pones un grupo dentro de un lookahead, también puedes encontrar coincidencias solapadas. El truco es avanzar la posición de una en una y sacar con el grupo los dos caracteres que hay en ese punto.

re.findall(r"(?=(\w\w))", "abc")   # ['ab', 'bc']

Laboratorio de pruebas

re.search(r"", text)

Valor devuelto

El reemplazo es una sola coma. El patrón tiene que encontrar posiciones, no caracteres; si no, desaparecen dígitos.

Caso resuelto (10 min)

Terminas el validador y filtras las candidatas. En la salida, 통과 significa «aprobada» y 탈락, «descartada».

pw = re.compile(r"(?=.*\d)(?=.*[a-z])(?=.*[^\w\s]).{8,}")
for c in cands:
    print(f"{c:<14} {'통과' if pw.fullmatch(c) else '탈락'}")
# vault2026!     통과
# vault2026      탈락
# VAULT2026!     탈락
# v!2            탈락
# longpassword!  탈락
# safe#2026box   통과

La directora no elige ninguna de las dos que han pasado. «En cuanto está en una lista de candidatas, ya no es una contraseña». Luego elige ella misma, sin que nadie la vea, una contraseña nueva que cumple las reglas, y guarda solo el validador en la caja de herramientas común de la agencia.

Informe de caso cerrado — Se creó una herramienta que comprueba las reglas de la contraseña de la caja fuerte con tres lookaheads. Solo dos de las seis candidatas cumplieron las reglas. La directora cambió la contraseña por su cuenta. Se añadieron separadores de miles a las cifras del libro de cuentas.

Ejercicios

  1. Añade a las reglas «al menos una mayúscula». ¿Qué candidatas sobreviven?
  2. En "price: 1200won, tax: 120won", saca con un lookahead solo los números que van delante de won.
  3. Predice el resultado de re.findall(r"(?<!\d)\d{2}(?!\d)", "1 22 333 44"). ¿Por qué no sale ningún trozo de 333?

El rastro del Backtracker

(a+)+$

La caja fuerte la dejo en paz. A cambio, le he enviado tres cartas al inspector Kang, de la comisaría. ¿Podrán demostrar que las escribió la misma persona? Las manías quedan en las letras.

La libreta de la directora

  • El lookaround es una coincidencia de ancho cero: no come caracteres, solo comprueba una posición (de la familia de ^ $ \b).

  • X(?=Y): X seguida de Y. X(?!Y): X no seguida de Y.

  • (?<=Y)X: X precedida de Y. (?<!Y)X: X no precedida de Y. El lookbehind solo admite longitud fija.

  • Lookaheads seguidos = AND: (?=.*\d)(?=.*[a-z])(?=.*[^\w\s]).{8,}

  • Si sub encuentra posiciones, intercala sin borrar nada: separadores de miles.

  • Con un grupo dentro de un lookahead, como (?=(\w\w)), puedes sacar coincidencias solapadas.

← Anterior

Lección 8 · El desafío del Backtracker: codicioso, perezoso y posesivo

Siguiente →

Lección 10 · El mismo modus operandi: retrorreferencias y condicionales

Recibe avisos de nuevas lecciones

Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.

Datos: tu correo electrónico · Finalidad: avisos de nuevas lecciones o de la publicación de este curso · Conservación: hasta que pidas que lo borremos. No lo usamos para nada más ni lo compartimos. Responde a cualquier aviso y lo borraremos enseguida.

Preguntas y respuestas

Pregunta lo que quieras sobre esta lección. Aquí se reúnen las preguntas de todas las versiones de idioma de esta página.

Se muestra como texto plano, sin enlaces ni formato.

R

ReflexKR

Aprende Python y Reflex haciendo. Todos los ejemplos se ejecutaron y verificaron en Reflex 0.10.0.

ReflexKR es gratis y sin banners publicitarios. Si te ayudó, puedes apoyarlo con un café. ☕ Invítanos un café

Aviso de afiliados
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex