Pregunta 1¿Qué devuelve re.match(r"\d+", "abc 123")?
Expresiones regulares re — Búsqueda y reemplazo de patrones
Aprende el módulo re de Python desde cero. Cubre cuándo recurrir a re.match / re.search / re.findall, combinar metacaracteres \d / \w / \s / * / + / ?, capturar grupos con ( ), sustitución con re.sub, y reutilizar patrones con re.compile — con ejercicios prácticos ejecutables.
Este artículo recorre el módulo re para expresiones regulares — «extraer y reemplazar las subcadenas que coinciden con un patrón específico». Cosas que haces constantemente en proyectos reales — analizar números de teléfono, emails, líneas de log y URLs — se vuelven one-liners.
Una herramienta para probar regex en vivo
Las expresiones regulares tienen muchas piezas en juego y son difíciles de razonar puramente en la cabeza. Para verificar si tu patrón coincide con lo que quieres, el Regex Extractor corre completamente en el navegador — escribe un patrón y algo de texto y mira las coincidencias en tiempo real. Mantenerlo abierto junto a este artículo hace mucho más fácil seguir.
match, search y findall — Tres funciones de búsqueda y cuándo usar cada una
El módulo re expone varias funciones de búsqueda, y eliges entre tres dependiendo de qué necesites. Los nombres son descriptivos — match coincide al inicio, search busca una coincidencia en cualquier lugar, y findall las encuentra todas. El rango de búsqueda exacto, tipo de retorno y comportamiento sin coincidencia se resumen en la siguiente tabla.
| Función | Rango de búsqueda | Retorno | Sin coincidencia |
|---|---|---|---|
| re.match | Solo el inicio de la cadena | Match object | None |
| re.search | Primera coincidencia en cualquier lugar | Match object | None |
| re.findall | Todas las coincidencias | Lista de cadenas | Lista vacía [] |
Del Match object que devuelven re.match y re.search (un objeto que contiene la posición del match, la cadena coincidente y la info de grupos), lees la cadena coincidente llamando su método `.group()` — m.group() o m.group(0) para el match completo, y (con los grupos de captura introducidos más adelante) m.group(1) para solo lo que estaba dentro de los paréntesis. Solo re.findall devuelve una lista directamente, así que no llamas .group() sobre ella.
| Metacarácter | Significado | Ejemplo |
|---|---|---|
| \d | Un solo dígito (0-9) | \d+ → uno o más dígitos |
| \w | Un carácter de palabra (alfanumérico + guion bajo) | \w+ → IDs y palabras clave |
| \s | Un carácter de espacio (espacio / tab / salto de línea) | Separadores |
| . | Cualquier carácter excepto salto de línea | Comodín |
| * | Cero o más del anterior | a* → vacío también vale |
| + | Uno o más del anterior | a+ → al menos uno |
| ? | Cero o uno del anterior | Opcional |
| [abc] | Uno entre a / b / c | Elección |
| ^ / $ | Inicio / fin de cadena | Anclas |
import re
text = "user_id: 12345, age: 30"
# match: desde el inicio (\w+ es una secuencia de caracteres de palabra)
m = re.match(r"\w+", text)
print(m.group()) # user_id
# search: primera secuencia de dígitos en cualquier lugar
s = re.search(r"\d+", text)
print(s.group()) # 12345
# findall: cada secuencia de dígitos
nums = re.findall(r"\d+", text)
print(nums) # ['12345', '30']
Escribe regex como una raw string r"..."
Las contrabarras aparecen por todos lados en regex. Una cadena normal "\d" puede tener sus escapes interpretados por la capa de string antes de que re la vea, así que es más seguro escribir la raw string `r"\d"` con la r al inicio. Los editores también suelen resaltar las raw strings como regex, lo que mejora la legibilidad.
Grupos de captura — Sacar partes específicas de un patrón
Cualquier cosa que pongas dentro de `( )` dentro de un regex se vuelve un grupo de captura — en lugar de solo el match completo, puedes sacar cada pieza por separado. Patrones como r"#(\d+) on (\d{4})-(\d{2})-(\d{2})" te permiten dividir un número de pedido y una fecha de una línea de log de un tirón.
Llama `.group(N)` sobre el Match object para leer el N-ésimo grupo (numerado desde 1). .group(0) (o .group() sin argumentos) devuelve el match completo.
.group(1) / .group(2). .group(0) es el match completo.import re
text = "Order #1234 placed on 2024-03-15"
# Lo que significa el patrón:
# # → '#' literal
# (\d+) → uno o más dígitos → group(1) número de pedido
# placed on → 'placed on' literal
# (\d{4}) → 4 dígitos → group(2) año
# (\d{2}) → 2 dígitos → group(3) mes
# (\d{2}) → 2 dígitos → group(4) día
m = re.search(r"#(\d+) placed on (\d{4})-(\d{2})-(\d{2})", text)
if m:
print("whole:", m.group(0)) # #1234 placed on 2024-03-15
print("order #:", m.group(1)) # 1234
print("year:", m.group(2)) # 2024
print("month:", m.group(3)) # 03
print("day:", m.group(4)) # 15
Llamar .group() cuando Match es None lanza error
Cuando re.search no encuentra el patrón devuelve None. Llamar m.group() sobre eso crashea con AttributeError: 'NoneType' object has no attribute 'group'. Siempre comprueba con `if m:` primero antes de .group(), o haz ambos en un paso usando el operador morsa: if m := re.search(...): ....
re.sub — Reemplazar coincidencias de patrón
«Enmascarar PII de un log», «quitar etiquetas HTML y mantener el cuerpo de texto», «normalizar una mezcla de espacios de ancho completo y medio» — todos se reducen a «reescribir cualquier cosa que coincida con un patrón en otra cosa». El replace de string solo maneja subcadenas fijas, pero re.sub lo hace por patrón.
`re.sub(patrón, reemplazo, original)` devuelve una nueva cadena con cada coincidencia reemplazada por el reemplazo. La cadena original está sin cambios (las cadenas Python son inmutables, así que siempre trabajas con el valor de retorno).
import re
# Enmascarar dígitos en un número de teléfono (reemplazar cada \d con un *)
text = "Tel: 03-1234-5678"
masked = re.sub(r"\d", "*", text)
print(masked)
# Tel: **-****-****
# Quitar etiquetas HTML para quedarse solo con el cuerpo de texto
html = "<p>Hola <b>Mundo</b></p>"
plain = re.sub(r"<[^>]+>", "", html)
print(plain)
# Hola Mundo
re.compile — Reutilizar un patrón
Cuando usas el mismo regex repetidamente, escribir re.search(r"...", text) una y otra vez hace que el motor parsee (compile) el patrón cada vez, lo cual es trabajo desperdiciado. `re.compile(patrón)` construye un objeto patrón compilado una vez, y llamas métodos sobre él como pattern.search(...) / pattern.findall(...) / pattern.sub(...). El código se lee mejor y corre más rápido.
.search / .findall / .sub tantas veces como necesites. Compila cuando reutilices el mismo patrón.import re
# Reutilizar el mismo patrón de número de teléfono
phone_re = re.compile(r"\d{2,4}-\d{4}-\d{4}")
print(phone_re.findall("03-1234-5678 o 080-1111-2222"))
# ['03-1234-5678', '080-1111-2222']
print(phone_re.search("mi teléfono es 03-9999-0000").group())
# 03-9999-0000
print(phone_re.sub("<phone>", "Contacto: 03-1234-5678"))
# Contacto: <phone>
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2¿Cuál es el regex correcto para uno o más dígitos en una fila?
Pregunta 3De re.search(r"(\w+)@(\w+)", "alice@example"), ¿qué llamada devuelve solo el dominio?
Pregunta 4¿Cuál es la razón principal para usar una raw string `r"..."` al escribir regex en Python?