Las expresiones regulares (también conocidas como regex o regexp) son secuencias de caracteres que forman un patrón de búsqueda. Son una herramienta fundamental para el procesamiento de texto y la recuperación de información, permitiendo buscar, validar, extraer y manipular cadenas de texto de forma eficiente y flexible.
En el contexto de las estructuras de datos y la recuperación de información, las expresiones regulares son esenciales para:
Búsqueda y extracción de información en grandes volúmenes de texto
Validación de datos (emails, teléfonos, URLs, etc.)
Procesamiento de logs y archivos de texto
Web scraping y extracción de datos de páginas web
Limpieza y transformación de datos antes de almacenarlos en estructuras de datos
1¿Qué son las expresiones regulares?¶
Una expresión regular es un patrón que describe un conjunto de cadenas de texto. Por ejemplo, el patrón r"\d{3}-\d{4}" describe cualquier cadena que tenga tres dígitos, seguidos de un guion, seguidos de cuatro dígitos (como "123-4567").
Las expresiones regulares utilizan una sintaxis especial con metacaracteres que tienen significados especiales. Estos metacaracteres permiten definir patrones complejos de forma concisa.
1.1Ejemplo simple¶
import re
texto = "Mi teléfono es 555-1234"
patron = r"\d{3}-\d{4}"
resultado = re.search(patron, texto)
if resultado:
print(f"Encontrado: {resultado.group()}")
else:
print("No encontrado")Output
Encontrado: 555-1234
2Sintaxis básica y metacaracteres¶
Las expresiones regulares utilizan caracteres especiales (metacaracteres) que tienen significados específicos. Veamos los más importantes:
2.1Caracteres literales¶
Los caracteres normales (letras, números) se buscan literalmente:
import re
texto = "Python es un lenguaje de programación"
patron = r"Python"
if re.search(patron, texto):
print("Se encontró 'Python'")Output
Se encontró 'Python'
2.2Metacaracteres básicos¶
.(punto)- Coincide con cualquier carácter excepto salto de línea
^(circunflejo)- Coincide con el inicio de la cadena
$(signo de dólar)- Coincide con el final de la cadena
*(asterisco)- Coincide con 0 o más repeticiones del patrón anterior
+(más)- Coincide con 1 o más repeticiones del patrón anterior
?(interrogación)- Coincide con 0 o 1 repetición del patrón anterior
[](corchetes)- Define un conjunto de caracteres
|(barra vertical)- Operador OR (alternativa)
()(paréntesis)- Agrupa expresiones y captura coincidencias
\(barra invertida)- Escapa metacaracteres o define secuencias especiales
2.3Ejemplos de metacaracteres¶
Punto (.), cualquier caracter:
re.findall(r"c.sa", "casa cosa cesa")Output
['casa', 'cosa', 'cesa']Inicio y fin de línea:
re.search(r"^Hola", "Hola mundo")Output
<re.Match object; span=(0, 4), match='Hola'>re.search(r"mundo$", "Hola mundo")Output
<re.Match object; span=(5, 10), match='mundo'>Asterisco (*), 0 o más ocurrencias:
re.findall(r"lo*", "l lo loo looo")Output
['l', 'lo', 'loo', 'looo']Más (+), 1 o más ocurrencias:
re.findall(r"lo+", "l lo loo looo")Output
['lo', 'loo', 'looo']Signo de interrogación (?), 0 o sólo 1 ocurrencia:
re.findall(r"lo?", "l lo loo")Output
['l', 'lo', 'lo']2.4Cuantificadores¶
Los cuantificadores especifican cuántas veces debe aparecer el patrón anterior:
{n}- Exactamente n repeticiones
re.findall(r"\d{3}", "El código es 12, 123, 1234 y 12345")Output
['123', '123', '123']{n,}- Al menos n repeticiones
re.findall(r"\d{3,}", "El código es 12, 123, 1234 y 12345")Output
['123', '1234', '12345']{,m}- Hasta m repeticiones
re.findall(r"\d{,3}", "El código es 12, 123, 1234 y 12345")Output
['',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'12',
'',
'',
'123',
'',
'',
'123',
'4',
'',
'',
'',
'123',
'45',
'']{n,m}- Entre n y m repeticiones
re.findall(r"\d{2,4}", "El código es 12, 123, 1234 y 12345")Output
['12', '123', '1234', '1234']2.5Clases de caracteres¶
Las clases de caracteres permiten definir conjuntos de caracteres válidos:
[abc]- Cualquiera de los caracteres a, b o c
[a-z]- Cualquier letra minúscula
[A-Z]- Cualquier letra mayúscula
re.findall(r"[A-Z]", "El código postal es A1234BCZ")Output
['E', 'A', 'B', 'C', 'Z'][0-9]- Cualquier dígito
re.findall(r"[0-9]+", "El código postal es A1234BCZ")Output
['1234'][^abc]- Cualquier carácter excepto a, b o c
Letras y números
re.findall(r"[A-Z0-9]+", "El código postal es A1234BCZ")Output
['E', 'A1234BCZ']Todo excepto espacios
re.findall(r"[^ ]+", "El código postal es A1234BCZ")Output
['El', 'código', 'postal', 'es', 'A1234BCZ']2.6Secuencias especiales¶
Python proporciona atajos para clases de caracteres comunes:
\d- Cualquier dígito (equivalente a
[0-9])
re.findall(r"\d+", "Usuario: juan_123, Email: juan@email.com")Output
['123']\D- Cualquier no-dígito
\w- Cualquier carácter de palabra: letra, dígito o guion bajo (equivalente a
[a-zA-Z0-9_])
re.findall(r"\w+", "Usuario: juan_123, Email: juan@email.com")Output
['Usuario', 'juan_123', 'Email', 'juan', 'email', 'com']\W- Cualquier no-carácter de palabra
\s- Cualquier espacio en blanco (espacio, tab, salto de línea)
\S- Cualquier no-espacio en blanco
\b- Límite de palabra
re.findall(r"\bjuan\b", "Usuario: juan_123, Email: juan@email.com")Output
['juan']re.findall(r"\bjuan", "juanito juan")Output
['juan', 'juan']\B- No-límite de palabra
3El módulo re en Python¶
Python proporciona el módulo re para trabajar con expresiones regulares. Este módulo incluye varias funciones útiles:
3.1Funciones principales¶
re.search(patron, texto)- Busca la primera ocurrencia del patrón en el texto
re.match(patron, texto)- Busca el patrón solo al inicio del texto
re.findall(patron, texto)- Devuelve todas las ocurrencias del patrón como una lista
re.finditer(patron, texto)- Devuelve un iterador con todas las ocurrencias
re.sub(patron, reemplazo, texto)- Reemplaza las ocurrencias del patrón
re.split(patron, texto)- Divide el texto usando el patrón como separador
re.compile(patron)- Compila el patrón para reutilizarlo eficientemente
3.2Ejemplos de uso¶
import re
texto = "Los emails son: juan@email.com, ana@empresa.com.ar y pedro@sitio.org"
# search: encuentra la primera coincidencia
if resultado := re.search(r"\w+@\w+\.\w+", texto):
print(f"Primer email encontrado: {resultado.group()}")
# findall: encuentra todas las coincidencias
emails = re.findall(r"\w+@[\w.]+", texto)
print(f"Todos los emails: {emails}")
# finditer: iterador sobre las coincidencias
for match in re.finditer(r"\w+@[\w.]+", texto):
print(f"Email en posición {match.start()}-{match.end()}: {match.group()}")Output
Primer email encontrado: juan@email.com
Todos los emails: ['juan@email.com', 'ana@empresa.com.ar', 'pedro@sitio.org']
Email en posición 16-30: juan@email.com
Email en posición 32-50: ana@empresa.com.ar
Email en posición 53-68: pedro@sitio.org
3.3Compilación de patrones¶
Cuando se usa un patrón múltiples veces, es más eficiente compilarlo:
import re
# Compilar el patrón una sola vez
patron_email = re.compile(r"\w+@[\w.]+")
textos = [
"Contacto: juan@email.com",
"Soporte: ayuda@empresa.com",
"Ventas: ventas@sitio.org",
]
for texto in textos:
if match := patron_email.search(texto):
print(f"{texto} -> {match.group()}")Output
Contacto: juan@email.com -> juan@email.com
Soporte: ayuda@empresa.com -> ayuda@empresa.com
Ventas: ventas@sitio.org -> ventas@sitio.org
3.4Grupos de captura¶
Los paréntesis () crean grupos de captura que permiten extraer partes específicas del patrón:
import re
texto = "Fecha: 15/03/2024"
patron = r"(\d{2})/(\d{2})/(\d{4})"
if match := re.search(patron, texto):
print(f"Fecha completa: {match.group(0)}") # Toda la coincidencia
print(f"Día: {match.group(1)}") # Primer grupo
print(f"Mes: {match.group(2)}") # Segundo grupo
print(f"Año: {match.group(3)}") # Tercer grupo
print(f"Todos los grupos: {match.groups()}") # Tupla con todos los gruposOutput
Fecha completa: 15/03/2024
Día: 15
Mes: 03
Año: 2024
Todos los grupos: ('15', '03', '2024')
3.5Grupos nombrados¶
Se pueden asignar nombres a los grupos para mayor claridad:
import re
texto = "Producto: ABC-123 Precio: $450.50"
patron = r"(?P<codigo>[A-Z]+-\d+).*?\$(?P<precio>\d+\.\d+)"
if match := re.search(patron, texto):
print(f"Código: {match.group('codigo')}")
print(f"Precio: {match.group('precio')}")
print(f"Diccionario: {match.groupdict()}")Output
Código: ABC-123
Precio: 450.50
Diccionario: {'codigo': 'ABC-123', 'precio': '450.50'}
3.6Miradas alrededor¶
Las miradas alrededor permiten hacer coincidir un patrón solo si está precedido o seguido por otro patrón, sin incluirlo en la coincidencia:
(?=...)- Mirada hacia adelante positiva (lookahead)
# foo seguido de un dígito
re.findall(r"foo(?=\d)", "foo1 bar2 foo3 baz4 foo baz")Output
['foo', 'foo'](?!...)- Mirada hacia adelante negativa
(?<=...)- Mirada hacia atrás positiva (lookbehind)
# dígito precedido de foo
re.findall(r"(?<=foo)\d", "foo1 bar2 foo3 baz4 5 6")Output
['1', '3'](?<!...)- Mirada hacia atrás negativa
4Casos de uso en recuperación de información¶
Las expresiones regulares son fundamentales en la recuperación y procesamiento de información. Veamos casos prácticos:
4.1Validación de datos¶
import re
def validar_email(email):
"""Valida un email con expresión regular"""
patron = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return re.match(patron, email) is not None
def validar_telefono_argentina(telefono):
"""Valida teléfono argentino: +54 11 1234-5678"""
patron = r"^\+54\s?\d{2}\s?\d{4}-?\d{4}$"
return re.match(patron, telefono) is not None
def validar_url(url):
"""Valida una URL"""
patron = r"^https?://(www\.)?[\w.-]+\.[a-z]{2,}(/[\w.-]*)*/?$"
return re.match(patron, url) is not None
# Pruebas
print(f"Email válido: {validar_email('juan@email.com')}")
print(f"Email inválido: {validar_email('juan@email')}")
print(f"Teléfono válido: {validar_telefono_argentina('+54 11 1234-5678')}")
print(f"URL válida: {validar_url('https://www.ejemplo.com/path')}")Output
Email válido: True
Email inválido: False
Teléfono válido: True
URL válida: True
4.2Extracción de información¶
import re
# Texto de ejemplo: log de servidor web
log = """
192.168.1.1- -[15/Mar/2024:10:30:45 +0000] "GET /index.html HTTP/1.1" 200 1234
10.0.0.5- -[15/Mar/2024:10:31:12 +0000] "POST /api/data HTTP/1.1" 201 567
192.168.1.1- -[15/Mar/2024:10:32:33 +0000] "GET /styles.css HTTP/1.1" 200 8910
"""
# Patrón para extraer información del log
patron = (
r"(\d+\.\d+\.\d+\.\d+).*?\[([^\]]+)\]" # IP y fecha
r'\s+"(\w+)\s+([^\s]+).*?"\s+(\d+)\s+(\d+)' # Método, ruta, código y tamaño
)
for linea in log.strip().split("\n"):
if match := re.search(patron, linea):
ip, fecha, metodo, ruta, codigo, tamaño = match.groups()
print(f"IP: {ip}")
print(f"Fecha: {fecha}")
print(f"Método: {metodo}")
print(f"Ruta: {ruta}")
print(f"Código: {codigo}")
print(f"Tamaño: {tamaño} bytes")
print("---")Output
IP: 192.168.1.1
Fecha: 15/Mar/2024:10:30:45 +0000
Método: GET
Ruta: /index.html
Código: 200
Tamaño: 1234 bytes
---
IP: 10.0.0.5
Fecha: 15/Mar/2024:10:31:12 +0000
Método: POST
Ruta: /api/data
Código: 201
Tamaño: 567 bytes
---
IP: 192.168.1.1
Fecha: 15/Mar/2024:10:32:33 +0000
Método: GET
Ruta: /styles.css
Código: 200
Tamaño: 8910 bytes
---
4.3Limpieza y normalización de texto¶
import re
def limpiar_texto(texto):
"""Limpia un texto para procesamiento"""
# Eliminar URLs
texto = re.sub(r"https?://\S+", "", texto)
# Eliminar emails
texto = re.sub(r"\S+@\S+", "", texto)
# Eliminar múltiples espacios
texto = re.sub(r"\s+", " ", texto)
# Eliminar caracteres especiales (mantener letras, números y espacios)
texto = re.sub(r"[^\w\s]", "", texto)
return texto.strip()
texto_sucio = """
Visita https://ejemplo.com para más info!
Contacto: info@ejemplo.com
Múltiples espacios aquí...
¿Caracteres especiales? @#$%
"""
print("Texto original:")
print(texto_sucio)
print()
print("Texto limpio:")
print(limpiar_texto(texto_sucio))Output
Texto original:
Visita https://ejemplo.com para más info!
Contacto: info@ejemplo.com
Múltiples espacios aquí...
¿Caracteres especiales? @#$%
Texto limpio:
Visita para más info Contacto Múltiples espacios aquí Caracteres especiales
4.4Tokenización de texto¶
def tokenizar(texto):
"""Divide un texto en palabras (tokens)"""
# Encontrar todas las secuencias de caracteres de palabra
return re.findall(r"\b\w+\b", texto.lower())4.5Desafíos en la Tokenización¶
Aunque parezca simple, la tokenización presenta varios “casos de borde” que deben resolverse según el dominio Manning et al., 2008:
Apóstrofes: ¿
vaca'ses un token o dos (vacays)?Guiones: ¿
anti-inflamatorioson dos palabras o una?Acrónimos:
U.N.T.R.E.F.debería ser reconocido como una única entidad.Extensiones y Números: Los puntos en las IPs (
192.168.1.1) o en los archivos (imagen.png) no deben ser tratados como fin de oración.
5Normalización y Limpieza con Regex¶
Más allá de dividir el texto, es necesario normalizar los tokens para que diferentes variantes de una palabra coincidan en el índice.
- Case folding
- Convertir todo a minúsculas (aunque a veces se mantienen mayúsculas en acrónimos como
FEDvsfed). - Eliminación de puntuación y stopwords
- Uso de regex para limpiar caracteres no alfanuméricos redundantes.
def normalizar_token(token):
# Eliminar puntuación al inicio/final del token
token = re.sub(r"^[^\w]+|[^\w]+$", "", token)
return token.lower()5.1El Costo Computacional de Regex¶
En sistemas de recuperación de información a gran escala —de millones de documentos—, el uso de expresiones regulares muy complejas (como las que usan backtracking) puede ralentizar significativamente la fase de indexación. Siempre se busca un balance entre la precisión del patrón y el tiempo de ejecución.
# Contar frecuencia de palabras
from collections import Counter
texto = "Python es un lenguaje de programación. ¡Es genial!"
tokens = tokenizar(texto)
print(f"Tokens: {tokens}")
print(f"Total de tokens: {len(tokens)}")
frecuencias = Counter(tokens)
print(f"Palabras más comunes: {frecuencias.most_common(3)}")Output
Tokens: ['python', 'es', 'un', 'lenguaje', 'de', 'programación', 'es', 'genial']
Total de tokens: 8
Palabras más comunes: [('es', 2), ('python', 1), ('un', 1)]
6Aplicaciones en procesamiento de texto¶
6.1Búsqueda de patrones en archivos¶
import re
# Crear un archivo de ejemplo
contenido_archivo = """
Estructuras de datos
Python es un lenguaje de programación versátil
Expresiones regulares son herramientas poderosas
Python se usa en ciencia de datos
Las estructuras de datos son fundamentales
"""
with open("ejemplo.txt", "w") as f:
f.write(contenido_archivo)
def buscar_en_archivo(archivo, patron):
"""Busca un patrón en un archivo y devuelve las líneas que coinciden"""
resultados = []
with open(archivo, "r") as f:
for num_linea, linea in enumerate(f, 1):
if re.search(patron, linea, re.IGNORECASE):
resultados.append((num_linea, linea.strip()))
return resultados
# Buscar líneas que contengan "Python" o "datos"
patron = r"Python|datos"
resultados = buscar_en_archivo("ejemplo.txt", patron)
print("Líneas encontradas:")
for num, linea in resultados:
print(f"{num}: {linea}")Output
Líneas encontradas:
2: Estructuras de datos
3: Python es un lenguaje de programación versátil
5: Python se usa en ciencia de datos
6: Las estructuras de datos son fundamentales
6.2Extracción de datos estructurados¶
import re
# HTML de ejemplo
html = """
<div class="producto">
<h2>Laptop HP</h2>
<p class="precio">$899.99</p>
<p>Procesador Intel i7</p>
</div>
<div class="producto">
<h2>Mouse Logitech</h2>
<p class="precio">$25.50</p>
<p>Inalámbrico</p>
</div>
"""
def extraer_productos(html):
"""Extrae información de productos del HTML"""
# Patrón para encontrar bloques de productos
patron_producto = r'<div class="producto">.*?<h2>(.*?)</h2>.*?<p class="precio">\$([\d.]+)</p>'
productos = re.findall(patron_producto, html, re.DOTALL)
return [
{"nombre": nombre, "precio": float(precio)}
for nombre, precio in productos
]
productos = extraer_productos(html)
for i, prod in enumerate(productos, 1):
print(f"Producto {i}: {prod['nombre']} - ${prod['precio']}")Output
Producto 1: Laptop HP - $899.99
Producto 2: Mouse Logitech - $25.5
6.3Reemplazo y transformación de texto¶
import re
def anonimizar_datos(texto):
"""Anonimiza datos sensibles en un texto"""
# Anonimizar emails
texto = re.sub(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", "[EMAIL]", texto)
# Anonimizar teléfonos (formato: xxx-xxxx o (xxx) xxx-xxxx)
texto = re.sub(r"\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}", "[TELÉFONO]", texto)
# Anonimizar números de tarjeta (grupos de 4 dígitos)
texto = re.sub(r"\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b", "[TARJETA]", texto)
return texto
texto_sensible = """
Contacto: juan.perez@email.com
Teléfono: 555-1234 o (555) 555-5678
Tarjeta: 1234 5678 9012 3456
"""
print("Texto original:")
print(texto_sensible)
print("\nTexto anonimizado:")
print(anonimizar_datos(texto_sensible))Output
Texto original:
Contacto: juan.perez@email.com
Teléfono: 555-1234 o (555) 555-5678
Tarjeta: 1234 5678 9012 3456
Texto anonimizado:
Contacto: [EMAIL]
Teléfono: 555-1234 o [TELÉFONO]
Tarjeta: [TARJETA]
6.4Análisis de texto y extracción de métricas¶
import re
from collections import Counter
def analizar_texto(texto):
"""Analiza un texto y extrae métricas"""
# Contar oraciones (terminan en . ! ?)
oraciones = re.split(r"[.!?]+", texto)
num_oraciones = len([s for s in oraciones if s.strip()])
# Contar palabras
palabras = re.findall(r"\b\w+\b", texto.lower())
num_palabras = len(palabras)
# Palabras más comunes
frecuencias = Counter(palabras)
palabras_comunes = frecuencias.most_common(5)
# Contar números
numeros = re.findall(r"\b\d+\b", texto)
# Detectar emails
emails = re.findall(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", texto)
# Detectar URLs
urls = re.findall(r"https?://[^\s]+", texto)
return {
"oraciones": num_oraciones,
"palabras": num_palabras,
"palabras_comunes": palabras_comunes,
"numeros": numeros,
"emails": emails,
"urls": urls,
}
texto_ejemplo = """
Python es un lenguaje de programación versátil.
Se utiliza en ciencia de datos, desarrollo web y automatización.
El curso tiene 50 estudiantes y 5 profesores!
Más información en https://ejemplo.com
Contacto: info@ejemplo.com
Python es muy popular. Python es fácil de aprender.
"""
metricas = analizar_texto(texto_ejemplo)
print("Análisis del texto:")
print(f"Oraciones: {metricas['oraciones']}")
print(f"Palabras: {metricas['palabras']}")
print(f"Palabras más comunes: {metricas['palabras_comunes']}")
print(f"Números encontrados: {metricas['numeros']}")
print(f"Emails encontrados: {metricas['emails']}")
print(f"URLs encontradas: {metricas['urls']}")Output
Análisis del texto:
Oraciones: 7
Palabras: 44
Palabras más comunes: [('python', 3), ('es', 3), ('de', 3), ('en', 2), ('y', 2)]
Números encontrados: ['50', '5']
Emails encontrados: ['info@ejemplo.com']
URLs encontradas: ['https://ejemplo.com']
7Flags (modificadores)¶
Las expresiones regulares en Python admiten varios flags que modifican su comportamiento:
re.IGNORECASEore.I- Ignora mayúsculas/minúsculas
re.MULTILINEore.M^y$coinciden con inicio/fin de cada líneare.DOTALLore.S.coincide con cualquier carácter, incluyendo saltos de líneare.VERBOSEore.X- Permite escribir patrones más legibles con espacios y comentarios
import re
# IGNORECASE
texto = "Python python PYTHON"
print(re.findall(r"python", texto, re.IGNORECASE))
# MULTILINE
texto_multilinea = """Primera línea
Segunda línea
Tercera línea"""
print(re.findall(r"^.*línea", texto_multilinea, re.MULTILINE))
# VERBOSE: patrón más legible
patron_email = re.compile(
r"""
^ # Inicio de la cadena
[a-zA-Z0-9._%+-]+ # Usuario
@ # @
[a-zA-Z0-9.-]+ # Dominio
\. # .
[a-zA-Z]{2,} # Extensión
$ # Fin de la cadena
""",
re.VERBOSE,
)
print(patron_email.match("usuario@ejemplo.com"))Output
['Python', 'python', 'PYTHON']
['Primera línea', 'Segunda línea', 'Tercera línea']
<re.Match object; span=(0, 19), match='usuario@ejemplo.com'>
8Ejercicios prácticos¶
8.1Ejercicio 1: Validador de contraseñas¶
import re
def validar_contraseña(password):
"""
Valida que una contraseña cumpla con:
- Al menos 8 caracteres
- Al menos una letra mayúscula
- Al menos una letra minúscula
- Al menos un dígito
- Al menos un carácter especial
"""
if len(password) < 8:
return False, "Debe tener al menos 8 caracteres"
if not re.search(r"[A-Z]", password):
return False, "Debe tener al menos una mayúscula"
if not re.search(r"[a-z]", password):
return False, "Debe tener al menos una minúscula"
if not re.search(r"\d", password):
return False, "Debe tener al menos un dígito"
if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
return False, "Debe tener al menos un carácter especial"
return True, "Contraseña válida"
# Pruebas
contraseñas = ["123456", "abcdefgh", "Abcdefgh", "Abcdefgh1", "Abcdefgh1!"]
for pwd in contraseñas:
valida, mensaje = validar_contraseña(pwd)
print(f"{pwd}: {mensaje}")Output
123456: Debe tener al menos 8 caracteres
abcdefgh: Debe tener al menos una mayúscula
Abcdefgh: Debe tener al menos un dígito
Abcdefgh1: Debe tener al menos un carácter especial
Abcdefgh1!: Contraseña válida
8.2Ejercicio 2: Extractor de información de texto¶
import re
def extraer_informacion_contacto(texto):
"""Extrae emails, teléfonos y URLs de un texto"""
# Patrón para emails
email_pattern = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
emails = re.findall(email_pattern, texto)
# Patrón para teléfonos (varios formatos)
telefonos = re.findall(
r"\+?\d{1,3}?[-.\s]?\(?\d{1,4}\)?[-.\s]?\d{1,4}[-.\s]?\d{1,9}", texto
)
# Patrón para URLs
urls = re.findall(r"https?://[^\s]+", texto)
return {"emails": emails, "telefonos": telefonos, "urls": urls}
texto_contacto = """
Para más información:
- Email: contacto@empresa.com o soporte@empresa.com.ar
- Teléfono: +54 11 4567-8900 o 555-1234
- Web: https://www.empresa.com y https://soporte.empresa.com/ayuda
"""
info = extraer_informacion_contacto(texto_contacto)
print("Información extraída:")
print(f"Emails: {info['emails']}")
print(f"Teléfonos: {info['telefonos']}")
print(f"URLs: {info['urls']}")Output
Información extraída:
Emails: ['contacto@empresa.com', 'soporte@empresa.com.ar']
Teléfonos: ['+54 11 4567', '8900', '555-1234']
URLs: ['https://www.empresa.com', 'https://soporte.empresa.com/ayuda']
8.3Ejercicio 3: Procesador de menciones y hashtags¶
import re
def procesar_tweet(texto):
"""Extrae menciones y hashtags de un texto estilo Twitter"""
# Menciones: @usuario
menciones = re.findall(r"@(\w+)", texto)
# Hashtags: #etiqueta
hashtags = re.findall(r"#(\w+)", texto)
# URLs
urls = re.findall(r"https?://\S+", texto)
# Texto limpio (sin menciones, hashtags ni URLs)
texto_limpio = re.sub(r"@\w+|#\w+|https?://\S+", "", texto)
texto_limpio = re.sub(r"\s+", " ", texto_limpio).strip()
return {
"menciones": menciones,
"hashtags": hashtags,
"urls": urls,
"texto_limpio": texto_limpio,
}
tweet = """
Gran clase de @profesorEDD sobre #ExpresionesRegulares!
#Python es genial para #DataScience
Más info: https://ejemplo.com/curso
cc: @estudiante1 @estudiante2
"""
resultado = procesar_tweet(tweet)
print("Análisis del tweet:")
print(f"Menciones: {resultado['menciones']}")
print(f"Hashtags: {resultado['hashtags']}")
print(f"URLs: {resultado['urls']}")
print(f"Texto limpio: {resultado['texto_limpio']}")Output
Análisis del tweet:
Menciones: ['profesorEDD', 'estudiante1', 'estudiante2']
Hashtags: ['ExpresionesRegulares', 'Python', 'DataScience']
URLs: ['https://ejemplo.com/curso']
Texto limpio: Gran clase de sobre ! es genial para Más info: cc:
9Rendimiento y buenas prácticas¶
9.1Compilar patrones reutilizables¶
Cuando se usa un patrón múltiples veces, es más eficiente compilarlo:
import re
import time
texto = "Python es genial. Python es versátil. Python es popular." * 1000
# Sin compilar
start = time.perf_counter()
for _ in range(1000):
re.findall(r"Python", texto)
end = time.perf_counter()
tiempo_sin_compilar = end - start
# Con compilación
patron = re.compile(r"Python")
start = time.perf_counter()
for _ in range(1000):
patron.findall(texto)
end = time.perf_counter()
tiempo_con_compilar = end - start
print(f"Sin compilar: {tiempo_sin_compilar:.4f} segundos")
print(f"Con compilar: {tiempo_con_compilar:.4f} segundos")
print(f"Mejora: {tiempo_sin_compilar / tiempo_con_compilar:.2f}x más rápido")Output
Sin compilar: 0.1708 segundos
Con compilar: 0.1654 segundos
Mejora: 1.03x más rápido
9.2Evitar backtracking excesivo¶
Algunas expresiones pueden causar backtracking excesivo y ser muy lentas:
import re
# Patrón ineficiente con backtracking
# patron_malo = r"(a+)+" # Puede ser muy lento
# Mejor: usar cuantificadores específicos
patron_bueno = r"a+"
texto = "a" * 20 + "b"
print(re.search(patron_bueno, texto))Output
<re.Match object; span=(0, 20), match='aaaaaaaaaaaaaaaaaaaa'>
9.3Usar raw strings¶
Siempre usar raw strings (r"...") para evitar problemas con caracteres de escape:
import re
# Sin raw string (necesita doble escape)
patron1 = "\\d+\\s+\\w+"
# Con raw string (más legible)
patron2 = r"\d+\s+\w+"
texto = "123 palabras"
print(re.search(patron1, texto).group())
print(re.search(patron2, texto).group())Output
123 palabras
123 palabras
10Integración con estructuras de datos¶
Las expresiones regulares se integran naturalmente con las estructuras de datos de Python:
10.1Índice invertido con regex¶
import re
from collections import defaultdict
class IndiceInvertido:
"""Índice invertido que usa regex para procesar documentos"""
def __init__(self):
self.indice = defaultdict(set)
def agregar_documento(self, doc_id, texto):
"""Agrega un documento al índice"""
# Tokenizar usando regex
palabras = re.findall(r"\b\w+\b", texto.lower())
for palabra in palabras:
self.indice[palabra].add(doc_id)
def buscar(self, patron):
"""Busca documentos que contengan palabras que coincidan
con el patrón"""
patron_compilado = re.compile(patron, re.IGNORECASE)
documentos = set()
for palabra in self.indice.keys():
if patron_compilado.search(palabra):
documentos.update(self.indice[palabra])
return documentos
# Crear índice
indice = IndiceInvertido()
indice.agregar_documento(1, "Python es un lenguaje de programación")
indice.agregar_documento(2, "Programar en Python es divertido")
indice.agregar_documento(3, "Java y JavaScript son diferentes")
# Buscar documentos que contengan palabras que empiecen con "prog"
docs = indice.buscar(r"^prog")
print(f"Documentos con palabras que empiezan con 'prog': {docs}")
# Buscar documentos con palabras que contengan "python"
docs = indice.buscar(r"python")
print(f"Documentos con 'python': {docs}")Output
Documentos con palabras que empiezan con 'prog': {1, 2}
Documentos con 'python': {1, 2}
10.2Filtrado de listas con regex¶
import re
# Lista de archivos
archivos = [
"documento1.txt",
"imagen.png",
"datos.csv",
"reporte_2024.pdf",
"script.py",
"backup_2024_03_15.zip",
]
def filtrar_archivos(archivos, patron):
"""Filtra archivos que coincidan con el patrón"""
patron_compilado = re.compile(patron)
return [archivo for archivo in archivos if patron_compilado.search(archivo)]
# Filtrar archivos de texto
print("Archivos .txt:", filtrar_archivos(archivos, r"\.txt$"))
# Filtrar archivos de 2024
print("Archivos de 2024:", filtrar_archivos(archivos, r"2024"))
# Filtrar archivos Python
print("Archivos Python:", filtrar_archivos(archivos, r"\.py$"))Output
Archivos .txt: ['documento1.txt']
Archivos de 2024: ['reporte_2024.pdf', 'backup_2024_03_15.zip']
Archivos Python: ['script.py']
11Tablas de referencia rápida¶
Descargar la versión imprimible
11.1Caracteres¶
| Expresión | Significado | Ejemplo | Match |
|---|---|---|---|
\d | En la mayoría de los lenguajes un dígito 0..9 | file_\d\d | file_25 |
| En Python 3 y .Net un dígito Unicode | file_\d\d | file_2੩ | |
\w | En la mayoría de los lenguajes, un carácter de palabra: letra, dígito o ‘_’ | \w-\w\w\w | A-f_3 |
| En Python 3, un símbolo Unicode de palabra, incluye ‘_’ | \w-\w\w\w | 字-ま\_۳ | |
| En .NET, un símbolo Unicode de palabra, incluye conector ‘‿’ | \w-\w\w\w | 字-ま‿۳ | |
\s | En la mayoría de los lenguajes caracteres de blanco estándar | a\sb\sc | a b c |
| En la .NET, Python 3, Javascript, caracteres de blanco Unicode | a\sb\sc | a b c | |
\D | Un caracter que no es un dígito \d del lenguaje | \D\D\D | ABC |
\W | Un caracter que no es un caracter de palabra \w del lenguaje | \W\W\W\W | \*+=) |
\S | Un caracter que no es un blanco estandar \s del lenguaje | \S\S\S\S | casa |
. | Cualquier caracter, excepto saltos de líneas | a.c | abc |
.* | piso 2, depto "A" | ||
\. | Un punto | \w\.\d | a.3 |
\ | Escape de caracteres especiales | \*\?\$\^ | *?\$^ |
\[\{\(\)\}\] | [{()}] |
11.2Cuantificadores¶
| Expresión | Significado | Ejemplo | Match |
|---|---|---|---|
+ | Una o más apariciones | \w-\w+ | C-125x_1 |
{3} | Exactamente tres apariciones | \D{3} | ANA |
{2,4} | Entre dos y cuatro apariciones | \W{2,4} | {+} |
* | Cero o más aparaciones | A*B*C* | AAAACCCC |
? | Cero o una aparición | casas? | casa |
11.3Lógica¶
| Expresión | Significado | Ejemplo | Match |
|---|---|---|---|
| | Or | 22|33 | 22 |
(...) | Captura un grupo y lo asocia a una variable numerada | UN(O|TREF) | UNTREF (y captura TREF) |
\1 | Lo capturado en el grupo 1 | r(\w)g\1\x | regex |
\2 | Lo capturado en el grupo 2 | (\d+)+(\d+)=\2+\1 | 25+33=33+25 |
(?:…) | Grupo que no se captura (se verifica la regex pero no se captura) | A(?:na|licia) | Alicia |
11.4Clases de caracteres¶
| Expresión | Significado | Ejemplo | Match |
|---|---|---|---|
[...] | Uno de los caracteres entre corchetes | [AEIOU] | A |
- | Indicador de rango | [a-z] | Una letra minúscula |
[A-Z]+ | Una o más letras mayúsculas | ||
[AB1-5w-z] | Uno de los caracteres AB12345wxyz | ||
[^x] | Cualquier caracter distinto de x | A[^a]B | AxB |
[^x-y] | Cualquier caracter fuera del rango x-y | [^a-z]{3} | A1! |
[\xhh] | El caracter con código hh en hexadecimal de la tabla de símbolos ASCII | [\x41-\x45]{3} | ABE |
11.5Posiciones: fronteras y anclas¶
| Expresión | Significado | Ejemplo | Match |
|---|---|---|---|
^ | Indicador de comienzo de cadena (o comienzo de línea). Tiene que estar fuera de [ ] (ya que adentro de [ ] significa negación) | ^abc.* | Texto que empieza con abc |
$ | Fin de cadena o fin de línea | .*el final\.$ | Texto que termina en el final. |
\b | Frontera de la palabra | Bibi.*\bes\b.* | Bibi es mi amiga |
\B | No es frontera de palabra | Bibi.*\Bes\B.* | Bibi usa un vestido |
11.6Miradas alrededor (look behind y look ahead)¶
No consumen caracteres, se quedan paradas donde ocurrió el matching
| Expresión | Significado | Ejemplo | Match |
|---|---|---|---|
(?=…) | Mirar hacia adelante con parámetro positivo | (?=\d{10})\d{5} | Si hacia adelante hay 10 dígitos matchear los primeros 5 |
(?<=…) | Mirar hacia atrás con parámetro positivo | (?<=foo).* | Si lo que está justo detrás de la posición actual es la cadena foo. El matching es todo lo que sigue a foo |
(?!…) | Mirar hacia adelante con parámetro negativo | q(?!ue) | matchea una q no este seguida de ue |
(?!teatro)te\w+ | cualquier palabra que empiece con te pero no sea teatro | ||
(?<!…) | Mirar hacia atrás con parámetro negativo | (?<!fut)bol | bol siempre y cuando no esté precedida por fut |
12Recursos adicionales¶
Para profundizar en expresiones regulares:
- Manning, C. D., Raghavan, P., & Schütze, H. (2008). Introduction to Information Retrieval. Cambridge University Press. https://nlp.stanford.edu/IR-book/