Las expresiones regulares describen patrones de texto. Pueden localizar fechas en un documento, extraer identificadores de registros, normalizar espacios, validar formatos sencillos o dividir entradas que utilizan varios separadores. Python ofrece esta funcionalidad mediante el módulo estándar re.
En esta guía de regex en Python aprenderás cadenas raw, clases de caracteres, anclas, cuantificadores, grupos, alternancia, search(), fullmatch(), findall(), finditer(), sub(), split(), flags, patrones compilados y reglas de seguridad.
Las expresiones regulares son potentes, pero no siempre son la mejor solución. Los métodos de cadenas en Python resultan más claros para reemplazos literales, prefijos fijos y divisiones sencillas. Los formatos estructurados como JSON o HTML deben procesarse con parsers específicos.
Importar el módulo re
import reNo necesitas instalar paquetes. Consulta la documentación oficial de re y el HOWTO de expresiones regulares.
Utilizar cadenas raw
Las expresiones regulares y las cadenas de Python utilizan barras invertidas. El prefijo r evita que Python procese la mayoría de los escapes antes de enviar el patrón al motor regex.
patron = r"\d{4}-\d{2}-\d{2}"Las cadenas raw facilitan la lectura. Aun así, no pueden terminar con una única barra invertida.
Caracteres literales y metacaracteres
.: cualquier carácter excepto salto de línea, normalmente.^: inicio de cadena o línea.$: final de cadena o línea.*: cero o más repeticiones.+: una o más repeticiones.?: cero o una repetición, o modificador no codicioso.{m,n}: entre m y n repeticiones.[...]: clase de caracteres.(...): grupo.|: alternancia.
Escapa un metacarácter para buscarlo literalmente:
patron_precio = r"\$\d+\.\d{2}"Cuando insertes texto externo dentro de un patrón, usa re.escape(texto).
Clases de caracteres
r"[abc]" # a, b o c
r"[a-z]" # letra minúscula ASCII del rango
r"[^0-9]" # carácter que no es un dígito ASCII
r"\d" # dígito Unicode por defecto
r"\w" # alfanumérico o guion bajo
r"\s" # espacio en blanco
r"\D" # no es un dígito
r"\W" # no es carácter de palabra
r"\S" # no es espacio\d puede coincidir con dígitos Unicode. Utiliza [0-9] o el flag ASCII cuando el formato exija solamente esos caracteres.
Buscar la primera coincidencia
re.search() recorre el texto y devuelve el primer objeto Match o None.
import re
texto = "El pedido 4832 está listo"
coincidencia = re.search(r"\d+", texto)
if coincidencia:
print(coincidencia.group())
print(coincidencia.start())
print(coincidencia.span())Comprueba siempre que exista una coincidencia antes de llamar a group(). La guía sobre None en Python explica cómo distinguir la ausencia de una coincidencia de otros valores falsos.
match(), search() y fullmatch()
match()comprueba solamente al inicio.search()busca en cualquier posición.fullmatch()exige que toda la cadena cumpla el patrón.
patron = r"[A-Z]{3}-[0-9]{4}"
print(re.match(patron, "ABC-1234 extra"))
print(re.search(patron, "Matrícula: ABC-1234"))
print(re.fullmatch(patron, "ABC-1234"))Para validar el formato completo de un valor, fullmatch() expresa mejor la intención.
Encontrar todas las coincidencias
texto = "Tickets: AB-104, XY-220, CD-981"
codigos = re.findall(r"[A-Z]{2}-[0-9]{3}", texto)
print(codigos)finditer() devuelve objetos Match de forma perezosa:
for coincidencia in re.finditer(r"[A-Z]{2}-[0-9]{3}", texto):
print(coincidencia.group(), coincidencia.span())Cuando también necesitas la posición de cada coincidencia, puedes combinar este patrón con enumerate() en Python para numerar resultados, líneas o registros.
Cuantificadores
r"a*" # cero o más
r"a+" # una o más
r"a?" # cero o una
r"a{3}" # exactamente tres
r"a{2,}" # al menos dos
r"a{2,5}" # entre dos y cincoLos cuantificadores afectan al token anterior. Agrupa una secuencia cuando debas repetirla completa:
r"(?:ab){2,4}"(?:...) es un grupo no capturante.
Coincidencia codiciosa y no codiciosa
texto = "<b>uno</b> y <b>dos</b>"
print(re.findall(r"<b>.*</b>", texto))
print(re.findall(r"<b>.*?</b>", texto))El segundo patrón usa *? para consumir la menor cantidad posible. El ejemplo sirve para comprender el comportamiento, pero HTML real debe procesarse con un parser.
Grupos de captura
patron_fecha = r"([0-9]{4})-([0-9]{2})-([0-9]{2})"
coincidencia = re.fullmatch(patron_fecha, "2026-07-11")
if coincidencia:
ano, mes, dia = coincidencia.groups()
print(ano, mes, dia)El patrón valida la forma, no si la fecha existe. Para reglas de calendario utiliza la guía de datetime en Python.
Grupos con nombre
patron = re.compile(
r"(?P<ano>[0-9]{4})-"
r"(?P<mes>[0-9]{2})-"
r"(?P<dia>[0-9]{2})"
)
coincidencia = patron.fullmatch("2026-07-11")
if coincidencia:
print(coincidencia.group("ano"))
print(coincidencia.groupdict())Los nombres facilitan la lectura de patrones complejos.
Alternancia
patron = r"gato|perro|pajaro"
print(re.findall(patron, "perro, gato y pajaro"))Agrupa la alternancia cuando forme parte de otro patrón:
patron_archivo = r"informe\.(?:csv|json|xlsx)"Reemplazar con sub()
texto = "Demasiados espacios"
limpio = re.sub(r"\s+", " ", texto).strip()
print(limpio)La sustitución también puede ser una función:
def ocultar_digitos(coincidencia):
return "*" * len(coincidencia.group())
oculto = re.sub(r"[0-9]+", ocultar_digitos, "Tarjeta terminada en 1234")
print(oculto)Para texto fijo, str.replace() suele ser más simple.
Dividir con un patrón
texto = "rojo, verde;azul | amarillo"
partes = re.split(r"\s*[,;|]\s*", texto)
print(partes)Para datos tabulares completos, conviene utilizar el módulo dedicado que se explica en la guía de archivos CSV con Python en lugar de intentar interpretar todas las columnas con una única expresión regular.
Compilar patrones reutilizados
patron_ticket = re.compile(r"[A-Z]{2}-[0-9]{3}")
for texto in ["AB-123", "invalido", "XY-999"]:
if patron_ticket.fullmatch(texto):
print("Válido:", texto)La compilación mejora la reutilización y hace explícita la configuración del patrón.
Flags de expresiones regulares
patron = re.compile(
r"^error:.*$",
flags=re.IGNORECASE | re.MULTILINE,
)
texto = "Info: inicio\nERROR: conexión fallida\nInfo: fin"
print(patron.findall(texto))re.IGNORECASE: ignora mayúsculas y minúsculas.re.MULTILINE: aplica^y$a cada línea.re.DOTALL: permite que el punto coincida con saltos.re.VERBOSE: permite espacios y comentarios.re.ASCII: limita ciertas clases abreviadas a ASCII.
Patrones legibles con VERBOSE
patron_correo = re.compile(
r"""
[A-Z0-9._%+-]+ # parte local
@
[A-Z0-9.-]+ # dominio
\.
[A-Z]{2,} # etiqueta final
""",
re.IGNORECASE | re.VERBOSE,
)Este ejemplo valida un formato común, no todos los correos posibles ni la existencia del buzón.
Proyecto práctico: analizar un log
Este proyecto combina expresiones regulares con Counter en Python, rutas gestionadas con pathlib y numeración de líneas mediante enumerate().
import re
from collections import Counter
from pathlib import Path
PATRON_LOG = re.compile(
r"^(?P<fecha>[0-9]{4}-[0-9]{2}-[0-9]{2}T"
r"[0-9]{2}:[0-9]{2}:[0-9]{2})\s+"
r"(?P<nivel>DEBUG|INFO|WARNING|ERROR|CRITICAL)\s+"
r"(?P<mensaje>.+)$"
)
def analizar_linea(linea):
coincidencia = PATRON_LOG.fullmatch(linea.strip())
return coincidencia.groupdict() if coincidencia else None
def analizar_archivo(ruta):
registros = []
lineas_invalidas = []
niveles = Counter()
with Path(ruta).open("r", encoding="utf-8", errors="replace") as archivo:
for numero, linea in enumerate(archivo, start=1):
registro = analizar_linea(linea)
if registro is None:
lineas_invalidas.append(numero)
continue
registros.append(registro)
niveles[registro["nivel"]] += 1
return registros, niveles, lineas_invalidasEn una aplicación real, los resultados y errores también pueden registrarse con el módulo logging de Python en lugar de depender únicamente de mensajes impresos.
Rendimiento y seguridad
Algunos patrones ambiguos pueden provocar retroceso excesivo con entradas especialmente construidas.
- Prefiere clases específicas a puntos repetidos.
- Evita cuantificadores anidados ambiguos.
- Limita la longitud de entradas no confiables.
- Prueba casos que deben fallar.
- Utiliza un parser cuando el contenido tenga una gramática real.
- Divide un problema complejo en varias validaciones simples.
Crea pruebas para coincidencias válidas, entradas rechazadas y casos límite. La guía de Pytest para principiantes muestra cómo organizar estas comprobaciones como pruebas de regresión.
Cuándo no utilizar regex
startswith()para prefijos fijos.- El
operador inde Python para buscar una subcadena sencilla. str.replace()para reemplazos literales.split()para un separador fijo.jsonpara JSON.- Un parser HTML o XML para marcado.
datetime.strptime()para validar fechas reales.
Errores frecuentes
- Olvidar el prefijo raw.
- Llamar a
group()sin comprobarNone. - Usar
match()cuando se necesitafullmatch(). - Agregar grupos capturantes y sorprenderse con el resultado de
findall(). - Suponer que
\dsignifica solamente dígitos ASCII. - Intentar analizar HTML arbitrario con regex.
- Escribir una expresión enorme donde varias comprobaciones serían más claras.
Conclusión
Regex en Python es útil cuando el problema depende realmente de patrones de texto. Utiliza cadenas raw, elige conscientemente entre search() y fullmatch(), emplea grupos con nombre para extracciones estructuradas y prueba entradas válidas e inválidas.
Mantén los patrones pequeños, documenta los complejos con modo verbose y cambia a un parser especializado cuando los datos tengan un formato definido.






