HDP115

Expresiones Regulares en Python

Aprende a utilizar expresiones regulares en Python para buscar, validar y manipular patrones de texto de manera eficiente.

CE

Cristian Escalante

Última actualización: 22 de mayo de 2025

python
programación
manipulación de texto

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:

  1. Caracteres literales: Coinciden consigo mismos (por ejemplo, a coincide con la letra "a")
  2. Metacaracteres: Caracteres especiales con significado dentro de una expresión regular
  3. 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

MetacaracterDescripció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

SecuenciaDescripción
\dCoincide con un dígito ([0-9])
\DCoincide con un no dígito ([^0-9])
\sCoincide con un espacio en blanco
\SCoincide con un no espacio en blanco
\wCoincide con un carácter alfanumérico ([a-zA-Z0-9_])
\WCoincide con un carácter no alfanumérico
\bCoincide con un límite de palabra
\BCoincide 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:

BanderaDescripción
re.IGNORECASE o re.IIgnora 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.XPermite 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

  1. Empieza simple y refina gradualmente: Comienza con una expresión regular simple y añade complejidad paso a paso.
  2. Usa herramientas de prueba: Utiliza sitios web como regex101.com para probar y depurar tus expresiones regulares.
  3. Comenta expresiones complejas: Usa el modo re.VERBOSE para añadir comentarios a expresiones regulares complejas.
  4. Evita la sobreoptimización: Las expresiones regulares muy complejas pueden ser difíciles de mantener. A veces, es mejor usar varias expresiones simples.
  5. Considera alternativas: Para algunos casos, puede ser más claro y eficiente usar métodos de cadenas como split(), startswith(), etc.
  6. Compila patrones reutilizados: Usa re.compile() para patrones que se usan repetidamente.
  7. Maneja casos especiales: Ten en cuenta caracteres especiales, diferentes codificaciones y casos límite.

Ejercicios prácticos

  1. 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).
  2. Desarrolla un validador de nombres de usuario que permita letras, números y guiones bajos, con una longitud entre 3 y 16 caracteres.
  3. Escribe una función que extraiga todas las etiquetas HTML de un texto.
  4. Implementa un analizador que extraiga todas las fechas de un texto en diferentes formatos (DD/MM/YYYY, MM-DD-YYYY, etc.).
  5. 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.

Análisis de Datos en Python
Aprende a utilizar bibliotecas como NumPy, Pandas y Matplotl...
Referencias
Python.org. Regular Expression HOWTO. https://docs.python.org/3/howto/regex.html
Python.org. re — Regular expression operations. https://docs.python.org/3/library/re.html

Conceptos Básicos de HTML

Aprende los conceptos básicos de HTML

Conceptos Básicos de CSS

Aprende los conceptos básicos de CSS

Conceptos Básicos de JavaScript

Aprende los conceptos básicos de JavaScript

Conceptos Básicos SQL

Aprende los conceptos básicos de SQL

Conceptos Básicos de GIT

Aprende los conceptos básicos de GIT

Conceptos Básicos de UML

Aprende los conceptos básicos de UML

Refuerzo Academico de Herramientas de Productividad 2025