Cuándo recurrir a regex
Las expresiones regulares son un pequeño lenguaje para describir patrones de texto. Son potentes y fáciles de usar en exceso.
Antes de recurrir a re, pregúntate si los métodos de string planos lo harían. .split(), .replace(), .startswith(), "target" in text — son más rápidos, más legibles y menos propensos a error que la regex equivalente. Guarda regex para cuando el patrón que te importa sea genuinamente estructurado pero demasiado flexible para operaciones fijas de string: direcciones de email, números de teléfono, líneas de log con formas conocidas, snippets de HTML o Markdown, cualquier búsqueda "encuentra este tipo de cosa".
El vocabulario básico
Un patrón regex es un string que describe lo que estás buscando. Unos cuantos bloques de construcción:
.— cualquier carácter único\d— cualquier dígito (0–9)\w— cualquier carácter de "palabra" (letra, dígito, guion bajo)\s— cualquier carácter de espacio en blanco^— inicio del string$— fin del string[abc]— cualquiera de a, b o c[^abc]— cualquier carácter excepto a, b, ca|b— a o b*— cero o más de lo anterior+— uno o más?— cero o uno{3}— exactamente 3{2,5}— entre 2 y 5( ... )— grupo (captura la coincidencia de dentro)
Eso es suficiente para la mayoría de regex del mundo real.
Las funciones clave
re.search(pattern, text) encuentra la primera coincidencia en cualquier sitio del string. Devuelve un objeto match o None:
Usa siempre un string raw (r"...") para patrones regex. Si no, Python intenta interpretar las barras invertidas como escapes de string y obtienes un patrón distinto al que tecleaste.
re.match(pattern, text) es como search pero solo hace match al principio:
La mayor parte del tiempo quieres search.
re.findall(pattern, text) devuelve todas las coincidencias no solapadas como lista:
Fíjate en que findall devuelve strings, no objetos match. Si tienes un patrón con un grupo, obtienes el contenido del grupo. Con varios grupos, obtienes una lista de tuplas.
Grupos de captura
Los paréntesis en un patrón capturan lo que haya coincidido dentro:
Los grupos con nombre suelen ser más claros:
Una vez que la regex tiene más de un grupo, nombrarlos hace el código de extracción mucho más fácil de seguir.
Reemplazar con re.sub
re.sub(pattern, replacement, text) reemplaza cada coincidencia:
Eso quita todo lo que no sea un dígito. El reemplazo puede referenciar grupos capturados con \1, \2, etc. — o en strings raw, \g<1> es más claro:
También puedes pasar una función como reemplazo. Eso es útil para transformaciones que no son un simple intercambio:
Compilar patrones para reutilizar
Si usas el mismo patrón muchas veces — especialmente en un bucle — compílalo una vez:
Los patrones compilados exponen los mismos métodos — search, match, findall, sub — sin el patrón como primer argumento. Ligeramente más eficiente, a menudo más legible.
Flags
Modificadores comunes, pasados como argumento flags= u OR'd juntos:
re.IGNORECASE(re.I) — coincidencia sin distinguir mayúsculas.re.MULTILINE(re.M) —^y$coinciden en cada línea, no solo en los límites del string.re.DOTALL(re.S) —.coincide también con saltos de línea (por defecto no).re.VERBOSE(re.X) — permite espacios en blanco y comentarios#en el patrón, para legibilidad.
re.VERBOSE es especialmente útil para patrones complejos:
Las regex multilínea y comentadas son muchísimo más fáciles de mantener que one-liners opacos.
Greedy vs lazy
Los cuantificadores (*, +, ?, {n,}) son greedy por defecto — hacen match de tanto como puedan. Añade un ? para hacerlos lazy:
La versión greedy captura toda la subcadena desde <b> hasta </i> — probablemente no lo que querías. El .+? lazy para en el primer >.
(Nota al margen: no parsees HTML con regex en producción. Usa html.parser o BeautifulSoup. El ejemplo de arriba es solo para ilustrar la greediness.)
Un ejemplo realista
Parsear líneas de un formato de log simple:
Eso es mucha potencia en no muchas líneas.
Unos cuantos hábitos
- Usa siempre strings raw para patrones.
- Empieza con el patrón más simple que funcione; afínalo después.
- Usa grupos con nombre cuando tengas más de un grupo.
- Compila los patrones que vayas a reutilizar.
- Regex es más lento que los métodos planos de string. Si
.split()sirve, usa.split().
Siguiente: errores y depuración
Eso cierra el tour de datos reales — archivos, JSON, CSV, HTTP, fechas y regex. Cualquier cosa en un programa real tarde o temprano se topa con un error, eso sí, y leer bien los tracebacks de Python es la habilidad de depuración más importante que puedes coger. El último capítulo cubre excepciones y los errores específicos con los que te encontrarás más a menudo.
Preguntas frecuentes
¿Qué es regex en Python?
Una expresión regular (regex) es un pequeño lenguaje para describir patrones en texto. El módulo re de Python te deja buscar patrones, extraer piezas y reemplazar coincidencias. Es excesivo para operaciones simples de string — usa métodos de string como .split() o .replace() primero — pero insuperable para coincidencias estructuradas de patrones.
¿Cuál es la diferencia entre re.match y re.search?
re.match solo hace match al principio del string. re.search escanea el string entero y encuentra la primera coincidencia en cualquier sitio. En caso de duda, usa search — coincide mejor con la intuición humana.
¿Debería usar siempre strings raw para patrones regex?
Sí. Los patrones regex a menudo contienen barras invertidas (\d, \s, \b), que los strings de Python interpretan como secuencias de escape. Prefijar con r — r'\d+' — le dice a Python que tome el string literalmente, lo que mantiene la regex en sí legible.