Expresiones regulares a la vista
·
Resumen del curso
1
El pago desaparecido: buscar el texto tal cual
2
El coche que se dio a la fuga: clases de caracteres
3
La guía telefónica rota: cuantificadores
4
Los registros falsos: anclas y validación
5
Fechas por todas partes: grupos y alternativas
6
El acceso de las 3 de la madrugada: grupos con nombre y objetos Match
7
La víspera de la noticia: sustitución y división
8
El desafío del Backtracker: codicioso, perezoso y posesivo
9
Las reglas de la caja fuerte: lookaround
10
El mismo modus operandi: retrorreferencias y condicionales
11
La regex ilegible: flags y compilación
12
El Backtracker, desenmascarado: repaso y un tokenizador
Tiempo 7:30–8:20
CASE-010
Grep & Co. Agencia de Detectives de Datos
Expediente
Cliente
El inspector Kang
Pruebas
Tres cartas de amenaza dirigidas al inspector Kang y una más que llegó ayer. La científica ha transcrito las cuatro, una por línea.
Para cerrar el caso
Averiguar qué cartas envió la misma persona y demostrarlo con patrones.
Una tarde de lluvia, el inspector Kang extiende cuatro bolsas de plástico sobre el escritorio. «Cartas de amenaza. Tres llegaron desde la semana pasada; la otra, ayer. Después de que saliera el artículo en el periódico». La directora se ajusta las gafas. «Entonces hay un imitador colado. Las pruebas dejan patrones. Lo que escribe una misma mano tiene las mismas manías».
Esta es la transcripción de la científica, una carta por línea. El cuerpo (msg=) es una frase corta en inglés.
#1 2026.10.18 key='K-17' tel=(010)-0000-1018 msg=the server server falls on friday
#2 2026-10-19 key="K-17" tel=010-0000-1019 msg=no one can can read me
#3 2026/10/20 key='K-17' tel=(010)-0000-1020 msg=see you At at the end
#4 2026-10/21 key="K-17' tel=(010-0000-1021 msg=pay now or elseA simple vista, las cuatro se parecen. Hoy vamos a buscar manías con la retrorreferencia, una sintaxis que pregunta: «¿vuelve a aparecer exactamente lo mismo que capturé antes?».
La directora señala la primera carta. «server server. Ha escrito la misma palabra dos veces. En la segunda también: can can». Parece un despiste, pero si está en las tres cartas, es una manía.
En la lección 5 aprendiste los números de grupo. Esos números también se pueden usar dentro del patrón. \1 significa «el mismo texto que acaba de capturar el grupo 1». A esto se le llama retrorreferencia (backreference).
import re
line = "msg=the server server falls on friday"
m = re.search(r"\b(\w+)\s+\1\b", line)
print(m.group()) # server server
print(m.group(1)) # server(\w+) captura server, \1 pasa a ser el texto server.\1 no vuelve a aplicar el patrón \w+. Por eso no coincide con dos palabras distintas.re.search(r"(\w+)\s+(\w+)", "the server").groups() # ('the', 'server')
re.search(r"(\w+)\s+\1", "the server") # NoneLos \b de los extremos también importan. Sin ellos, el can can de can cannot también contaría como «palabra repetida».
re.search(r"(\w+)\s+\1", "can cannot") # <re.Match object; span=(0, 7), match='can can'>
re.search(r"\b(\w+)\s+\1\b", "can cannot") # NoneLaboratorio de pruebas
re.search(r"", text)
Valor devuelto
En el resultado del laboratorio hay algo raro: falta el At at de la tercera carta. Es porque A y a son caracteres distintos. Con re.I (ignorar mayúsculas; lo veremos a fondo en la lección 11), la retrorreferencia también compara sin distinguir mayúsculas y minúsculas.
re.findall(r"\b(\w+)\s+\1\b", letters) # ['server', 'can']
re.findall(r"\b(\w+)\s+\1\b", letters, re.I) # ['server', 'can', 'At']
print(re.search(r"(a)\1", "aA", re.I)) # <re.Match object; span=(0, 2), match='aA'>Aquí letters es la cadena con las cuatro líneas de arriba. Según el resultado, la manía de repetir palabra solo aparece en las cartas 1, 2 y 3.
En la lección 5, la directora dejó un problema pendiente. Si eliges el separador con [-./], también pasan fechas mezcladas como 2026-10/20, porque la clase elige en cada posición por separado. La solución es capturar el primer separador con una retrorreferencia y exigir el mismo carácter en la segunda posición.
re.fullmatch(r"\d{4}[-./]\d\d[-./]\d\d", "2026-10/21") # <re.Match object; span=(0, 10), match='2026-10/21'>
re.fullmatch(r"\d{4}([-./])\d\d\1\d\d", "2026-10/21") # None
re.fullmatch(r"\d{4}([-./])\d\d\1\d\d", "2026/10/20") # <re.Match object; span=(0, 10), match='2026/10/20'>Los grupos con nombre (lección 6) también se pueden llamar dentro del patrón, con la sintaxis (?P=nombre). Cuando hay muchos grupos, los nombres se leen mejor que los números.
DATE = r"(?P<y>\d{4})(?P<sep>[-./])(?P<m>\d\d)(?P=sep)(?P<d>\d\d)"
m = re.search(DATE, "#3 2026/10/20 key='K-17'")
print(m.groupdict()) # {'y': '2026', 'sep': '/', 'm': '10', 'd': '20'}(?P=sep) podrías escribir \2.(?P=sep); en el reemplazo, \g<sep> (lección 7); en el objeto Match, m["sep"].Laboratorio de pruebas
re.search(r"", text)
Valor devuelto
Mira el valor que sigue a key=. Las cartas 1 y 3 lo envuelven en comillas simples, 'K-17', y la 2 en comillas dobles, "K-17". Solo la 4 abre con comillas dobles y cierra con simples: "K-17'. Si capturas la comilla de apertura en un grupo y exiges \1 al cerrar, sabrás si el par coincide.
QUOTED = r'(["\']).*?\1'
for line in letters.splitlines():
m = re.search(QUOTED, line)
print(line[:2], m.group() if m else None)
# #1 'K-17'
# #2 "K-17"
# #3 'K-17'
# #4 None\'. En una regex, \' es simplemente '..*? (cuantificador perezoso, lección 8) por un motivo: con .* se comería todo, desde la primera comilla hasta la última comilla igual.re.search(r'(["\']).*\1', '"a" and "b"').group() # '"a" and "b"'
re.search(r'(["\']).*?\1', '"a" and "b"').group() # '"a"'Laboratorio de pruebas
re.search(r"", text)
Valor devuelto
Pero si metes el mismo patrón en findall, el resultado es raro.
re.findall(QUOTED, letters) # ["'", '"', "'"]
[m.group() for m in re.finditer(QUOTED, letters)] # ["'K-17'", '"K-17"', "'K-17'"]Es la regla de la lección 5: si hay un grupo, findall devuelve solo ese grupo. Y esta vez no puedes cambiarlo por (?:...), porque desaparecería el grupo al que llama \1.
El consejo de Dot
La primera vez yo también junté parejas de comillas con findall y volví con la boca llena de comillas sueltas. Para usar una retrorreferencia hace falta un grupo sí o sí, así que cuando quiero la coincidencia entera, uso finditer y saco m.group(). También puedes envolver todo el patrón en otro paréntesis, pero entonces se corren los números y \1 pasa a ser \2.
Fíjate en el teléfono, tel=. Las cartas 1 y 3 ponen el prefijo entre paréntesis, como en (010)-0000-1018, y la 2 lo escribe sin paréntesis. La 4 solo tiene el de apertura: (010-0000-1021. La regla «si hubo paréntesis de apertura, tiene que haber de cierre» se escribe con un patrón condicional.
(?(1)sí|no) significa «si el grupo 1 participó en la coincidencia, aplica el patrón sí; si no, el patrón no». La rama no se puede omitir: (?(1)sí) significa «si el grupo participó, exige sí; si no, no exijas nada».
PHONE = r"(\()?\d{2,3}(?(1)\))-\d{3,4}-\d{4}"
for tel in ["(010)-0000-1018", "010-0000-1019", "(010-0000-1021", "010)-0000-1021"]:
print(tel, bool(re.fullmatch(PHONE, tel)))
# (010)-0000-1018 True
# 010-0000-1019 True
# (010-0000-1021 False
# 010)-0000-1021 False(\()?: captura el paréntesis de apertura como grupo 1, pero puede no estar.(?(1)\)): si el grupo 1 capturó algo, exige el paréntesis de cierre; si no, sigue adelante.Con grupos con nombre, la condición usa el nombre en vez del número: (?(nombre)sí|no). Aquí la regla es que si el número de lista va como (1), tiene que cerrarse con paréntesis, y si va sin paréntesis, como 2., tiene que acabar en punto.
ITEM = r"(?P<p>\()?\d+(?(p)\)|\.)"
for s in ["(1)", "2.", "(3.", "4)"]:
print(s, bool(re.fullmatch(ITEM, s)))
# (1) True
# 2. True
# (3. False
# 4) FalseLaboratorio de pruebas
re.search(r"", text)
Valor devuelto
Aunque cumplas todas las condiciones del caso, en la línea de la carta 4 del laboratorio 010-0000-1021 sigue resaltado. search y finditer pueden empezar a coincidir en cualquier posición, así que se saltan el paréntesis de apertura y empiezan justo detrás. Pon tel= delante para fijar el punto de partida.
re.search(PHONE, "tel=(010-0000-1021").group() # '010-0000-1021'
re.search(r"tel=" + PHONE + r"\b", "tel=(010-0000-1021") # NoneCuando el número de la retrorreferencia tiene dos cifras, es fácil confundirse. Dentro del patrón, \10 no es «el grupo 1 seguido del carácter 0», sino el grupo 10. Si no existe un grupo 10, es un error. Tampoco se interpreta como un carácter en octal.
re.compile(r"(a)\10")
# re.PatternError: invalid group reference 10 at position 4
p = re.compile(r"(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)\10")
print(p.fullmatch("abcdefghijj")) # <re.Match object; span=(0, 11), match='abcdefghijj'>\1[0].\g<1>0 del reemplazo (lección 7) no sirve dentro de un patrón. En un patrón, \g es un escape desconocido y da error.\0 o tiene tres cifras (\101 es A). Lo repasaremos en la lección 12.print(re.fullmatch(r"(a)\1[0]", "aa0")) # <re.Match object; span=(0, 3), match='aa0'>Escribe las cuatro manías como reglas y cuenta cuántas cumple cada carta. Los nombres de las reglas están en coreano: 반복 = repetición, 날짜 = fecha, 따옴표 = comillas, 괄호 = paréntesis.
RULES = {
"반복": re.compile(r"\b(\w+)\s+\1\b", re.I),
"날짜": re.compile(r"\d{4}([-./])\d\d\1\d\d"),
"따옴표": re.compile(r'key=(["\']).*?\1'),
"괄호": re.compile(r"tel=(\()?\d{2,3}(?(1)\))-\d{3,4}-\d{4}\b"),
}
for line in letters.splitlines():
hits = [name for name, rx in RULES.items() if rx.search(line)]
print(line[:2], hits)
# #1 ['반복', '날짜', '따옴표', '괄호']
# #2 ['반복', '날짜', '따옴표', '괄호']
# #3 ['반복', '날짜', '따옴표', '괄호']
# #4 []Las cartas 1, 2 y 3 comparten las cuatro manías: repiten una palabra, usan un único separador en las fechas y nunca fallan una pareja de comillas o de paréntesis. La 4 solo imita la apariencia y no tiene ni una.
El inspector Kang chasquea la lengua. «O sea, que la 4 es de algún gracioso que leyó el artículo, y las de verdad son tres». La directora asiente. «El de verdad es meticuloso. No falla ni una pareja».
Informe de caso cerrado — Las cartas de amenaza 1, 2 y 3 las envió la misma persona (el Backtracker). Coinciden en la palabra repetida, el separador de fecha único y las parejas de comillas y paréntesis. La 4 se considera una imitación y se entregó aparte al inspector Kang.
Cuando vas a cerrar el informe, la directora vuelve a tomar los originales. Hay líneas que la científica no transcribió. Al pie de cada una de las tres cartas auténticas, una línea extraña hecha solo de paréntesis y barras invertidas.
0000. Pista: a \1 también se le puede poner un cuantificador.<b>bold</b> <i>x</b>, busca solo los casos en que la etiqueta de apertura y la de cierre tienen el mismo nombre. El resultado debe ser solo <b>bold</b>.<, tenga que terminar en >. <kim@example.com> y kim@example.com deben pasar, y <kim@example.com debe fallar.El rastro del Backtracker
(a+)+$
La última línea de cada una de las tres. Únelas en orden. Saldrá una sola línea. Si son capaces de leerla.
La libreta de la directora
Dentro del patrón, \1 es exactamente el mismo texto que capturó el grupo 1. No vuelve a aplicar el patrón.
Palabra repetida: \b(\w+)\s+\1\b. Mismo separador de fecha: \d{4}([-./])\d\d\1\d\d.
Un grupo con nombre se vuelve a llamar con (?P=name). En el reemplazo, \g<name>; en el Match, m['name'].
Pareja de comillas: (["']).*?\1. findall devuelve solo las comillas, así que para la coincidencia entera usa finditer.
Condicionales: (?(1)sí|no), (?(name)sí|no). La rama «no» se puede omitir: (?(1)sí).
Con re.I, la retrorreferencia también ignora mayúsculas. \10 es el grupo 10; para un 0 tras el grupo 1, \1[0].
← Anterior
Lección 9 · Las reglas de la caja fuerte: lookaround
Siguiente →
Lección 11 · La regex ilegible: flags y compilación
Recibe avisos de nuevas lecciones
Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.
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© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
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.