Imagina que estás buscando una aguja en un pajar. Ahora imagina que tienes un imán que encuentra instantáneamente todas las agujas de una forma determinada. Así es como funcionan las expresiones regulares, una poderosa herramienta para encontrar y procesar patrones de texto que ahorra a los desarrolladores cientos de horas de trabajo rutinario.
¿Qué son las expresiones regulares?
Las expresiones regulares (regex o regexp) son un lenguaje formal para describir patrones de texto. Permiten encontrar, extraer y reemplazar texto de acuerdo con reglas complejas utilizando una sintaxis compacta. En lugar de escribir decenas de líneas de código para validar una dirección de correo electrónico, puedes usar una expresión regular.
Las expresiones regulares aparecieron en la década de 1950 gracias al matemático Stephen Kleene, pero se hicieron realmente populares con el desarrollo de los sistemas Unix y los editores de texto. Hoy en día son compatibles con casi todos los lenguajes de programación.
Sintaxis básica
Empecemos por lo fácil. La expresión regular más básica es una cadena normal. Por ejemplo, el patrón cat encontrará todas las coincidencias de la palabra «cat» en el texto. Pero el verdadero poder de las expresiones regulares se revela a través de caracteres especiales.
Los metasímbolos son la base de regex:
.: cualquier carácter excepto el salto de línea. El patrón c.t encontrará "cat", "cut", "c9t", etc.
^ — inicio de la línea. La expresión ^Hello encontrará "Hello" solo al principio de la línea.
$ — final de la línea. El patrón world$ encontrará "world" solo al final.
* - cero o más repeticiones del carácter anterior. Por ejemplo, go*gle encontrará "ggle", "gogle", "google", "goooogle".
+ — una o más repeticiones. El patrón go+gle encontrará "gogle", "google", pero no "ggle".
? — cero o una coincidencia. La expresión colou?r encontrará tanto "color" como "colour".
[] es una clase de caracteres. El patrón [aeiou] encontrará cualquier vocal, y [0-9] encontrará cualquier número.
| — lógico O. La expresión cat|dog encontrará "cat" o "dog".
() — agrupación. Los paréntesis permiten crear subexpresiones y capturar las partes necesarias del texto.
Ejemplos prácticos
Echemos un vistazo a los desafíos reales a los que se enfrentan los desarrolladores.
Validación de la dirección de correo electrónico:
import re
def validate_email(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
return bool(re.match(pattern, email))
print(validate_email("user@example.com")) # True
print(validate_email("invalid.email")) # FalseEsta expresión comprueba la estructura básica del correo electrónico: caracteres antes de @, nombre de dominio y zona.
Extracción de números de teléfono:
text = "Contacts: +7(999)123-45-67, 8-800-555-35-35"
pattern = r'[\+\d][\d\-\(\)]{9,}'
phones = re.findall(pattern, text)
print(phones) # ['+7(999)123-45-67', '8-800-555-35-35']Sustitución de datos sensibles:
text = "My card number: 1234-5678-9012-3456"
pattern = r'\d{4}-\d{4}-\d{4}-\d{4}'
masked = re.sub(pattern, '****-****-****-****', text)
print(masked) # Mi número de tarjeta: **** - **** - **** - ****Análisis de URL:
url = "https://example.com:8080/path/to/page?param=value#section"
pattern = r'(?P<protocol>https?://)?(?P<domain>[^:/]+)(?::(?P<port>\d+))?(?P<path>/[^?#]*)?(?:\?(?P<query>[^#]*))?(?:#(?P<fragment>.*))?'
match = re.match(pattern, url)
print(match.group('domain')) # example.com
print(match.group('port')) # 8080Técnicas avanzadas
Lookahead y lookbehind son herramientas potentes para la búsqueda contextual sin captura de texto.
Positive lookahead (?=...) — comprueba que un patrón específico va a continuación:
# Encuentra las palabras seguidas de un signo de exclamación
pattern = r'\w+(?=!)'
text = "Hi! How are you? Great!"
print(re.findall(pattern, text)) # ['Hola', 'Perfecto']]Negative lookahead (?!...) — comprueba que el patrón NO va más allá:
# Encontrar los números que no van seguidos del símbolo del rublo
pattern = r'\d+(?!₽)'
text = "100₽, 200, 300₽, 400"
print(re.findall(pattern, text)) # ['200', '400']Cuantificadores codiciosos y perezosos:
Por defecto, los cuantificadores son codiciosos: capturan el máximo de texto:
text = "<div>First</div><div>Second</div>"
pattern = r'<div>.*</div>'
print(re.findall(pattern, text))
# ['<div>Primero</div><div>Segundo</div>'] # ¡Lo captó todo!Al añadir ?, hacemos que el cuantificador sea perezoso:
pattern = r'<div>.*?</div>'
print(re.findall(pattern, text))
# ['<div>Primero</div>', '<div>Segundo</div>'] # Dos coincidencias separadasGrupos con nombre hacen que el código sea legible:
log = "2024-11-25 14:30:15 ERROR Database connection failed"
pattern = r'(?P<date>\d{4}-\d{2}-\d{2}) (?P<time>\d{2}:\d{2}:\d{2}) (?P<level>\w+) (?P<message>.*)'
match = re.match(pattern, log)
print(match.group('level')) # ERROR
print(match.group('message')) # Database connection failed
Rendimiento y optimización
Las expresiones regulares pueden ser lentas si se usan incorrectamente. Aquí tienes algunos consejos:
Compile patrones para su reutilización:
# Mal: se compila cada vez
for text in texts:
if re.match(r'\d{3}-\d{3}', text):
process(text)
# Bueno: se compila una vez
pattern = re.compile(r'\d{3}-\d{3}')
for text in texts:
if pattern.match(text):
process(text)Evita el backtracking catastrófico:
Algunos patrones pueden causar un crecimiento exponencial en el tiempo de ejecución:
# ¡Peligroso!
pattern = r'(a+)+'
text = "aaaaaaaaaaaaaaaaaaaaaaaX" # Puede colgarseEsto se debe a las múltiples rutas de coincidencia en caso de fallo. La solución es utilizar grupos atómicos o cuantificadores posesivos, donde se admitan.
Utiliza anclas:
# Más lento
pattern = r'\d{4}-\d{2}-\d{2}'
# Más rápido si la fecha está al principio de la línea
pattern = r'^\d{4}-\d{2}-\d{2}'Depuración de expresiones regulares
Las expresiones regulares pueden ser difíciles de entender. Utiliza herramientas de visualización y prueba:
regex101.com — una excelente herramienta en línea con explicaciones
debuggex.com — visualización de patrones en forma de diagramas
regexr.com — caja de arena interactiva con pistas
Para expresiones complejas, añade comentarios con la bandera re.VERBOSE:
pattern = re.compile(r'''
^ # Start of line
(?P<username> # Group for username
[a-zA-Z0-9_]{3,16} # From 3 to 16 alphanumeric characters
)
@ # Symbol @
(?P<domain> # Group for domain
[a-zA-Z0-9.-]+ # Domain name
\.[a-zA-Z]{2,} # Point and zone
)
$ # End of line
' de línea
''', re.VERBOSE)Alternativas a las expresiones regulares
Regex no siempre es la mejor solución. Para algunas tareas, es mejor usar herramientas especializadas:
Para el análisis de HTML, usa BeautifulSoup o lxml en lugar de regex: HTML no es un lenguaje regular y regex puede dar resultados impredecibles.
Para el análisis de JSON, utiliza las bibliotecas integradas. Para gramáticas complejas, considere generadores de analizadores como PLY o ANTLR.
Para una búsqueda simple de subcadenas, los métodos de cadena suelen ser más rápidos y fáciles de entender que las expresiones regulares.
Conclusión
Las expresiones regulares son una herramienta poderosa en el arsenal de un desarrollador. Ahorran tiempo, hacen que el código sea compacto y resuelven problemas que de otro modo requerirían decenas de líneas de código. Comienza con patrones simples, practica con tareas reales y gradualmente dominarás este lenguaje de patrones. Recuerda el rendimiento, usa herramientas de depuración y no tengas miedo de experimentar. Con el tiempo, las expresiones regulares se convertirán en una parte natural de tu flujo de trabajo.
¿Quieres profundizar en la programación y aprender a aplicar herramientas como las expresiones regulares en la práctica? La aplicación Kodik ofrece cursos estructurados de Python, JavaScript y desarrollo web para desarrolladores principiantes. Únete a nuestro canal de Telegram, donde encontrarás materiales útiles, análisis de temas complejos y el apoyo de una comunidad de personas afines dispuestas a ayudarte en tu viaje de programación.
