Expresiones Regulares en Python
Aprende a utilizar expresiones regulares en Python para buscar, validar y manipular patrones de texto de manera eficiente.
Cristian Escalante
Última actualización: 22 de mayo de 2025
Expresiones Regulares en Python
Las expresiones regulares (también conocidas como regex o regexp) son patrones de búsqueda que permiten encontrar y manipular texto de manera eficiente. Son una herramienta extremadamente poderosa para trabajar con cadenas de texto, validación de datos, extracción de información y mucho más.
Introducción a las expresiones regulares
Una expresión regular es una secuencia de caracteres que define un patrón de búsqueda. Python proporciona el módulo re
para trabajar con expresiones regulares.
Importando el módulo re
import re
Conceptos básicos
Las expresiones regulares pueden parecer complicadas al principio, pero se basan en algunos conceptos simples:
- Caracteres literales: Coinciden consigo mismos (por ejemplo,
a
coincide con la letra "a") - Metacaracteres: Caracteres especiales con significado dentro de una expresión regular
- Patrones: Combinaciones de caracteres literales y metacaracteres que definen lo que estamos buscando
Funciones principales del módulo re
re.search()
Busca un patrón en una cadena y devuelve el primer resultado encontrado:
import re
texto = "Python es un lenguaje de programación versátil"
resultado = re.search(r"Python", texto)
if resultado:
print("Encontrado:", resultado.group())
print("Posición:", resultado.start(), "-", resultado.end())
else:
print("No encontrado")
# Salida:
# Encontrado: Python
# Posición: 0 - 6
re.match()
Similar a search()
, pero solo busca al inicio de la cadena:
# Esto encuentra una coincidencia
resultado = re.match(r"Python", "Python es genial")
print(resultado.group() if resultado else "No encontrado") # Python
# Esto no encuentra coincidencia porque no está al inicio
resultado = re.match(r"genial", "Python es genial")
print(resultado.group() if resultado else "No encontrado") # No encontrado
re.findall()
Encuentra todas las coincidencias y devuelve una lista:
texto = "Python es un lenguaje de programación. Python es fácil de aprender."
coincidencias = re.findall(r"Python", texto)
print(coincidencias) # ['Python', 'Python']
# Encontrar todas las palabras
palabras = re.findall(r"\w+", texto)
print(palabras) # ['Python', 'es', 'un', 'lenguaje', 'de', 'programación', 'Python', 'es', 'fácil', 'de', 'aprender']
re.finditer()
Similar a findall()
, pero devuelve un iterador de objetos match:
texto = "Python es un lenguaje de programación. Python es fácil de aprender."
for coincidencia in re.finditer(r"Python", texto):
print(f"Encontrado '{coincidencia.group()}' en posición {coincidencia.start()}-{coincidencia.end()}")
# Salida:
# Encontrado 'Python' en posición 0-6
# Encontrado 'Python' en posición 35-41
re.sub()
Sustituye las coincidencias por otro texto:
texto = "Python es un lenguaje de programación. Python es fácil de aprender."
nuevo_texto = re.sub(r"Python", "JavaScript", texto)
print(nuevo_texto)
# Salida: JavaScript es un lenguaje de programación. JavaScript es fácil de aprender.
# Limitar el número de sustituciones
nuevo_texto = re.sub(r"Python", "JavaScript", texto, count=1)
print(nuevo_texto)
# Salida: JavaScript es un lenguaje de programación. Python es fácil de aprender.
re.split()
Divide una cadena según un patrón:
texto = "Python,Java,C++,JavaScript,Go"
lenguajes = re.split(r",", texto)
print(lenguajes) # ['Python', 'Java', 'C++', 'JavaScript', 'Go']
# Dividir por múltiples delimitadores
texto = "Python,Java;C++:JavaScript.Go"
lenguajes = re.split(r"[,;:.]", texto)
print(lenguajes) # ['Python', 'Java', 'C++', 'JavaScript', 'Go']
Metacaracteres y secuencias especiales
Metacaracteres básicos
Metacaracter | Descripción |
---|---|
. | Coincide con cualquier carácter excepto nueva línea |
^ | Coincide con el inicio de la cadena |
$ | Coincide con el final de la cadena |
* | Coincide con 0 o más repeticiones |
+ | Coincide con 1 o más repeticiones |
? | Coincide con 0 o 1 repetición |
{n} | Coincide exactamente con n repeticiones |
{n,} | Coincide con n o más repeticiones |
{n,m} | Coincide con entre n y m repeticiones |
\ | Escapa un metacaracter o introduce una secuencia especial |
[] | Conjunto de caracteres |
| | Alternativa (OR) |
() | Grupo |
Ejemplos de metacaracteres
# El punto (.) coincide con cualquier carácter
print(re.findall(r"p.t", "pat pet pit pot put")) # ['pat', 'pet', 'pit', 'pot', 'put']
# El acento circunflejo (^) coincide con el inicio de la cadena
print(re.findall(r"^Python", "Python es genial\nPython es fácil")) # ['Python']
# El signo de dólar ($) coincide con el final de la cadena
print(re.findall(r"Python$", "Es genial Python")) # ['Python']
# El asterisco (*) coincide con 0 o más repeticiones
print(re.findall(r"ab*c", "ac abc abbc abbbc")) # ['ac', 'abc', 'abbc', 'abbbc']
# El signo más (+) coincide con 1 o más repeticiones
print(re.findall(r"ab+c", "ac abc abbc abbbc")) # ['abc', 'abbc', 'abbbc']
# El signo de interrogación (?) coincide con 0 o 1 repetición
print(re.findall(r"ab?c", "ac abc abbc")) # ['ac', 'abc']
# Repeticiones específicas
print(re.findall(r"ab{2}c", "abc abbc abbbc")) # ['abbc']
print(re.findall(r"ab{2,}c", "abc abbc abbbc abbbbc")) # ['abbc', 'abbbc', 'abbbbc']
print(re.findall(r"ab{1,2}c", "abc abbc abbbc")) # ['abc', 'abbc']
Clases de caracteres
Las clases de caracteres permiten especificar conjuntos de caracteres:
# Conjunto de caracteres
print(re.findall(r"[aeiou]", "Hello World")) # ['e', 'o', 'o']
# Rango de caracteres
print(re.findall(r"[a-z]", "Hello 123")) # ['e', 'l', 'l', 'o']
print(re.findall(r"[0-9]", "Hello 123")) # ['1', '2', '3']
# Negación de conjunto
print(re.findall(r"[^aeiou]", "Hello")) # ['H', 'l', 'l']
Secuencias especiales
Secuencia | Descripción |
---|---|
\d | Coincide con un dígito ([0-9] ) |
\D | Coincide con un no dígito ([^0-9] ) |
\s | Coincide con un espacio en blanco |
\S | Coincide con un no espacio en blanco |
\w | Coincide con un carácter alfanumérico ([a-zA-Z0-9_] ) |
\W | Coincide con un carácter no alfanumérico |
\b | Coincide con un límite de palabra |
\B | Coincide con un no límite de palabra |
# Ejemplos de secuencias especiales
texto = "Python 3.9 es genial!"
print(re.findall(r"\d", texto)) # ['3', '9']
print(re.findall(r"\D", texto)) # ['P', 'y', 't', 'h', 'o', 'n', ' ', '.', ' ', 'e', 's', ' ', 'g', 'e', 'n', 'i', 'a', 'l', '!']
print(re.findall(r"\s", texto)) # [' ', ' ', ' ']
print(re.findall(r"\S", texto)) # ['P', 'y', 't', 'h', 'o', 'n', '3', '.', '9', 'e', 's', 'g', 'e', 'n', 'i', 'a', 'l', '!']
print(re.findall(r"\w", texto)) # ['P', 'y', 't', 'h', 'o', 'n', '3', '9', 'e', 's', 'g', 'e', 'n', 'i', 'a', 'l']
print(re.findall(r"\W", texto)) # [' ', '.', ' ', ' ', '!']
# Límites de palabra
print(re.findall(r"\bes\b", "Python es genial")) # ['es']
print(re.findall(r"\bes\b", "Esto es español")) # ['es']
Grupos y capturas
Los paréntesis ()
se utilizan para crear grupos de captura:
texto = "Python 3.9 fue lanzado en 2020"
# Capturar versión y año
resultado = re.search(r"Python (\d+\.\d+) fue lanzado en (\d+)", texto)
if resultado:
print("Versión:", resultado.group(1))
print("Año:", resultado.group(2))
print("Coincidencia completa:", resultado.group(0))
print("Todos los grupos:", resultado.groups())
# Salida:
# Versión: 3.9
# Año: 2020
# Coincidencia completa: Python 3.9 fue lanzado en 2020
# Todos los grupos: ('3.9', '2020')
Grupos con nombre
Puedes asignar nombres a los grupos para facilitar su referencia:
texto = "Python 3.9 fue lanzado en 2020"
resultado = re.search(r"Python (?P<version>\d+\.\d+) fue lanzado en (?P<año>\d+)", texto)
if resultado:
print("Versión:", resultado.group("version"))
print("Año:", resultado.group("año"))
# Salida:
# Versión: 3.9
# Año: 2020
Grupos no capturantes
Si solo necesitas agrupar pero no capturar, usa (?:...)
:
texto = "abc123def456"
# Encuentra secuencias de letras seguidas de números, pero solo captura los números
resultado = re.findall(r"(?:[a-z]+)(\d+)", texto)
print(resultado) # ['123', '456']
Cuantificadores codiciosos vs. no codiciosos
Por defecto, los cuantificadores (*
, +
, ?
, {n,m}
) son "codiciosos", es decir, intentan coincidir con la mayor cantidad de texto posible:
texto = "<div>Contenido 1</div><div>Contenido 2</div>"
# Cuantificador codicioso
resultado = re.findall(r"<div>.*</div>", texto)
print(resultado) # ['<div>Contenido 1</div><div>Contenido 2</div>']
# Cuantificador no codicioso (añadiendo ?)
resultado = re.findall(r"<div>.*?</div>", texto)
print(resultado) # ['<div>Contenido 1</div>', '<div>Contenido 2</div>']
Banderas de expresiones regulares
Las banderas modifican el comportamiento de las expresiones regulares:
Bandera | Descripción |
---|---|
re.IGNORECASE o re.I | Ignora mayúsculas/minúsculas |
re.MULTILINE o re.M | ^ y $ coinciden con inicio/fin de cada línea |
re.DOTALL o re.S | . coincide también con saltos de línea |
re.VERBOSE o re.X | Permite comentarios y espacios en el patrón |
# Ignorar mayúsculas/minúsculas
print(re.findall(r"python", "Python es genial", re.IGNORECASE)) # ['Python']
# Modo multilínea
texto = "Línea 1\nLínea 2\nLínea 3"
print(re.findall(r"^Línea", texto, re.MULTILINE)) # ['Línea', 'Línea', 'Línea']
# Modo DOTALL
texto = "Línea 1\nLínea 2"
print(re.findall(r"Línea.*2", texto)) # [] (sin coincidencias)
print(re.findall(r"Línea.*2", texto, re.DOTALL)) # ['Línea 1\nLínea 2']
# Modo VERBOSE
patron = re.compile(r"""
\d{3} # Tres dígitos
[-] # Un guion
\d{2} # Dos dígitos
[-] # Otro guion
\d{4} # Cuatro dígitos
""", re.VERBOSE)
print(patron.match("123-45-6789").group()) # 123-45-6789
Compilación de expresiones regulares
Para mejorar el rendimiento cuando usas la misma expresión regular múltiples veces, puedes compilarla:
# Sin compilar
for i in range(1000):
re.search(r"\d+", "abc123def")
# Compilando (más eficiente)
patron = re.compile(r"\d+")
for i in range(1000):
patron.search("abc123def")
Ejemplos prácticos
Validación de correo electrónico
def validar_email(email):
patron = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(patron, email))
# Ejemplos
emails = ["usuario@ejemplo.com", "usuario.nombre@ejemplo.co.uk",
"usuario@.com", "usuario@ejemplo", "@ejemplo.com"]
for email in emails:
print(f"{email}: {'Válido' if validar_email(email) else 'No válido'}")
# Salida:
# usuario@ejemplo.com: Válido
# usuario.nombre@ejemplo.co.uk: Válido
# usuario@.com: No válido
# usuario@ejemplo: No válido
# @ejemplo.com: No válido
Extracción de URLs de un texto
def extraer_urls(texto):
patron = r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+'
return re.findall(patron, texto)
texto = """
Visita nuestro sitio web en https://ejemplo.com para más información.
También puedes encontrarnos en http://ejemplo.org o en https://sub.ejemplo.net/pagina.
"""
urls = extraer_urls(texto)
for url in urls:
print(url)
# Salida:
# https://ejemplo.com
# http://ejemplo.org
# https://sub.ejemplo.net
Validación de contraseñas
def validar_contraseña(contraseña):
"""
Valida que una contraseña cumpla con los siguientes requisitos:
- Al menos 8 caracteres
- Al menos una letra mayúscula
- Al menos una letra minúscula
- Al menos un número
- Al menos un carácter especial
"""
# Verificar longitud
if len(contraseña) < 8:
return False
# Verificar mayúscula, minúscula, número y carácter especial
if not re.search(r'[A-Z]', contraseña):
return False
if not re.search(r'[a-z]', contraseña):
return False
if not re.search(r'\d', contraseña):
return False
if not re.search(r'[^A-Za-z0-9]', contraseña):
return False
return True
# Alternativa usando una sola expresión regular
def validar_contraseña_regex(contraseña):
patron = r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$'
return bool(re.match(patron, contraseña))
# Ejemplos
contraseñas = ["Abc123!", "abc123", "ABC123!", "Abcdefgh", "Abc123*", "Ab1!"]
for contraseña in contraseñas:
valida = validar_contraseña(contraseña)
print(f"{contraseña}: {'Válida' if valida else 'No válida'}")
# Salida:
# Abc123!: Válida
# abc123: No válida (falta mayúscula y carácter especial)
# ABC123!: No válida (falta minúscula)
# Abcdefgh: No válida (falta número y carácter especial)
# Abc123*: Válida
# Ab1!: No válida (menos de 8 caracteres)
Extracción de información estructurada
def extraer_info_producto(texto):
patron = r'Producto: (.*?), Precio: \$([\d.]+), Código: (\w+)'
coincidencias = re.findall(patron, texto)
productos = []
for coincidencia in coincidencias:
productos.append({
'nombre': coincidencia[0],
'precio': float(coincidencia[1]),
'codigo': coincidencia[2]
})
return productos
texto = """
Inventario:
Producto: Laptop HP, Precio: $899.99, Código: LAP123
Producto: Mouse inalámbrico, Precio: $24.50, Código: MOU456
Producto: Teclado mecánico, Precio: $59.99, Código: TEC789
"""
productos = extraer_info_producto(texto)
for producto in productos:
print(f"Nombre: {producto['nombre']}")
print(f"Precio: ${producto['precio']:.2f}")
print(f"Código: {producto['codigo']}")
print("-" * 30)
Sustitución con función
def convertir_a_celsius(match):
fahrenheit = float(match.group(1))
celsius = (fahrenheit - 32) * 5/9
return f"{celsius:.1f}°C"
texto = "Hoy la temperatura es de 68°F y ayer fue de 75°F."
# Sustituir temperaturas en Fahrenheit por Celsius
resultado = re.sub(r'(\d+(?:\.\d+)?)°F', convertir_a_celsius, texto)
print(resultado)
# Salida: Hoy la temperatura es de 20.0°C y ayer fue de 23.9°C.
Consejos y mejores prácticas
- Empieza simple y refina gradualmente: Comienza con una expresión regular simple y añade complejidad paso a paso.
- Usa herramientas de prueba: Utiliza sitios web como regex101.com para probar y depurar tus expresiones regulares.
- Comenta expresiones complejas: Usa el modo
re.VERBOSE
para añadir comentarios a expresiones regulares complejas. - Evita la sobreoptimización: Las expresiones regulares muy complejas pueden ser difíciles de mantener. A veces, es mejor usar varias expresiones simples.
- Considera alternativas: Para algunos casos, puede ser más claro y eficiente usar métodos de cadenas como
split()
,startswith()
, etc. - Compila patrones reutilizados: Usa
re.compile()
para patrones que se usan repetidamente. - Maneja casos especiales: Ten en cuenta caracteres especiales, diferentes codificaciones y casos límite.
Ejercicios prácticos
- Crea una expresión regular para validar números de teléfono en diferentes formatos (por ejemplo, 123-456-7890, (123) 456-7890, 123.456.7890).
- Desarrolla un validador de nombres de usuario que permita letras, números y guiones bajos, con una longitud entre 3 y 16 caracteres.
- Escribe una función que extraiga todas las etiquetas HTML de un texto.
- Implementa un analizador que extraiga todas las fechas de un texto en diferentes formatos (DD/MM/YYYY, MM-DD-YYYY, etc.).
- Crea un programa que valide y formatee números de tarjetas de crédito, ocultando todos los dígitos excepto los últimos cuatro.
En la próxima lección, exploraremos la programación concurrente en Python, que nos permitirá ejecutar múltiples tareas simultáneamente para mejorar el rendimiento de nuestras aplicaciones.