Python RegEx
Aprende expresiones regulares en Python: sintaxis, secuencias especiales, grupos, lookaheads, flags y funciones del módulo re con ejemplos claros.
Las expresiones regulares (regex) te permiten buscar, extraer y reemplazar texto basándote en patrones flexibles en lugar de cadenas exactas. El módulo integrado re de Python proporciona todo lo que necesitas. Este capítulo cubre el conjunto completo de herramientas de regex: sintaxis, secuencias especiales, cuantificadores, grupos, lookahead/lookbehind, flags y todas las funciones clave del módulo re, con ejemplos correctos y ejecutables a lo largo del texto.
¿Por qué usar expresiones regulares?
Los métodos de string simples (str.find(), str.replace(), str.split()) funcionan bien para texto fijo. Las expresiones regulares destacan cuando el patrón varía:
- Validar que un string tenga la forma de una dirección de correo electrónico o número de teléfono.
- Extraer todas las fechas de un documento, independientemente del formato exacto.
- Eliminar etiquetas HTML de un string.
- Reemplazar múltiples secuencias de espacios en blanco distintas por un único espacio.
Cuando la tarea implica describir una forma en lugar de un valor fijo, utiliza re.
Raw Strings
Los patrones de regex casi siempre se escriben como raw strings (r'...'). Los raw strings tratan las barras invertidas como caracteres literales, lo cual importa porque regex utiliza muchas secuencias con barra invertida (\d, \w, \s, \b). Sin el prefijo r necesitarías doble barra invertida en todas partes:
import re
# Both patterns are identical — raw string is easier to read
re.findall(r'\d+', 'abc 123') # raw: r'\d+'
re.findall('\\d+', 'abc 123') # normal: '\\d+'Usa raw strings para cada patrón de regex — es la convención universal.
Sintaxis básica: metacaracteres
Los metacaracteres son caracteres con significado especial dentro de un patrón. Los caracteres literales se corresponden exactamente consigo mismos.
| Metacarácter | Significado |
|---|---|
. | Cualquier carácter excepto un salto de línea |
^ | Inicio del string (o línea en modo MULTILINE) |
$ | Fin del string (o línea en modo MULTILINE) |
* | Cero o más del elemento anterior |
+ | Uno o más del elemento anterior |
? | Cero o uno del elemento anterior |
{n} | Exactamente n repeticiones |
{n,m} | Entre n y m repeticiones |
[...] | Clase de caracteres — cualquier carácter listado dentro |
[^...] | Clase negada — cualquier carácter no listado |
| | Alternación — la expresión izquierda o la derecha |
() | Grupo de captura |
\ | Escapa un metacarácter o inicia una secuencia especial |
Para hacer coincidir un metacarácter literal como . o *, escápalo con una barra invertida: \. coincide con un punto real.
Secuencias especiales
Las secuencias especiales son clases de caracteres abreviadas que aparecen con frecuencia en patrones del mundo real.
| Secuencia | Coincide con |
|---|---|
\d | Cualquier dígito — igual que [0-9] |
\D | Cualquier no dígito |
\w | Carácter de palabra: letras, dígitos, guion bajo |
\W | Carácter que no es de palabra |
\s | Espacio en blanco: espacio, tabulación, salto de línea |
\S | No espacio en blanco |
\b | Límite de palabra (anchura cero) |
\B | No límite |
import re
print(re.findall(r'\d+', 'I have 3 cats and 12 dogs'))
# ['3', '12']
print(re.findall(r'\w+', 'hello_world 123'))
# ['hello_world', '123']
print(re.findall(r'\bPython\b', 'Python Pythonista Python3'))
# ['Python'] — word boundary prevents partial matches\b es especialmente útil: coincide con la posición entre un carácter de palabra y un carácter que no lo es, por lo que \bPython\b coincide con la palabra independiente "Python" pero no con "Pythonista" o "Python3".
Cuantificadores
Los cuantificadores controlan cuántas veces debe coincidir el elemento anterior.
import re
print(re.findall(r'a*', 'baaa')) # ['', 'aaa', '']
print(re.findall(r'a+', 'baaa')) # ['aaa']
print(re.findall(r'a?', 'baaa')) # ['', 'a', 'a', 'a', '']
print(re.findall(r'a{3}', 'baaa')) # ['aaa']Coincidencia codiciosa vs. perezosa
Por defecto, los cuantificadores son codiciosos — coinciden con la mayor cantidad de texto posible. Añade ? después del cuantificador para hacerlo perezoso (coincidir con la menor cantidad posible).
import re
html = '<b>bold</b> and <i>italic</i>'
print(re.findall(r'<.*>', html))
# ['<b>bold</b> and <i>italic</i>'] — greedy: matches from first < to last >
print(re.findall(r'<.*?>', html))
# ['<b>', '</b>', '<i>', '</i>'] — lazy: matches each individual tagLos cuantificadores perezosos son esenciales cuando se analiza texto estructurado como HTML o fragmentos JSON.
Clases de caracteres
Una clase de caracteres [...] coincide con cualquier carácter del conjunto listado. Usa - para rangos y ^ al inicio para negar la clase.
import re
print(re.findall(r'[aeiou]', 'hello world'))
# ['e', 'o', 'o']
print(re.findall(r'[0-9]', 'a1b2c3'))
# ['1', '2', '3']
print(re.findall(r'[^aeiou\s]+', 'hello world'))
# ['h', 'll', 'w', 'rld'] — consonants only (not vowels, not spaces)Rangos predefinidos comunes: [a-z] letras minúsculas, [A-Z] mayúsculas, [0-9] dígitos, [a-zA-Z0-9] alfanumérico.
Anclas
Las anclas no consumen caracteres — afirman una posición en el string.
import re
print(re.findall(r'^Python', 'Python is great'))
# ['Python'] — matches only if 'Python' is at the start
print(re.findall(r'great$', 'Python is great'))
# ['great'] — matches only if 'great' is at the end
print(re.findall(r'^Python', 'Learn Python'))
# [] — 'Python' is not at the start of this stringConsulta el flag re.MULTILINE más adelante en este capítulo para aplicar ^ y $ por línea en lugar de por string.
Alternación
La barra vertical | funciona como un OR lógico entre dos expresiones.
import re
print(re.findall(r'cat|dog', 'I have a cat and a dog'))
# ['cat', 'dog']
print(re.findall(r'colou?r|colour', 'color and colour'))
# ['color', 'colour']Grupos de captura
Los paréntesis () crean un grupo de captura. Los grupos permiten extraer subpartes de una coincidencia. re.search() devuelve un objeto de coincidencia; llama a .group(n) o .groups() sobre él.
import re
match = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2023-10-05')
if match:
print(match.group(0)) # '2023-10-05' — entire match
print(match.group(1)) # '2023'
print(match.groups()) # ('2023', '10', '05')Grupos con nombre
Nombra un grupo con (?P<name>...) para acceder a él por nombre en lugar de por posición. Esto hace que los patrones sean mucho más fáciles de mantener.
import re
match = re.search(
r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
'2023-10-05'
)
if match:
print(match.group('year')) # '2023'
print(match.groupdict()) # {'year': '2023', 'month': '10', 'day': '05'}Grupos sin captura
Usa (?:...) cuando necesites agrupar pero no necesites capturar el valor.
import re
print(re.findall(r'(?:Mr|Mrs|Ms)\.? \w+', 'Mr. Smith and Mrs. Jones'))
# ['Mr. Smith', 'Mrs. Jones']Lookahead y Lookbehind
El lookahead ((?=...)) y el lookbehind ((?<=...)) afirman que algo sigue o precede a la coincidencia, sin incluirlo en el resultado. Son de anchura cero: no consumen caracteres.
import re
# Positive lookahead — find numbers followed by 'dollars'
print(re.findall(r'\d+(?= dollars)', '100 dollars and 200 euros'))
# ['100']
# Negative lookahead — find numbers NOT followed by 'dollars'
print(re.findall(r'\b\d+\b(?! dollars)', '100 dollars and 200 euros'))
# ['200']
# Positive lookbehind — extract domain from email addresses
emails = 'Contact [email protected] or [email protected]'
print(re.findall(r'(?<=@)\w+\.\w+', emails))
# ['example.com', 'test.org']Los patrones de lookbehind deben tener anchura fija en Python — no puedes usar * o + dentro de uno.
El módulo re: funciones principales
re.search() — Encontrar la primera coincidencia
Devuelve un objeto de coincidencia para la primera posición donde el patrón coincide, o None si no hay coincidencia.
import re
text = 'apple banana apple'
match = re.search(r'banana', text)
if match:
print(match.group()) # 'banana'
print(match.span()) # (6, 12)re.match() — Coincidir solo al inicio
Similar a re.search(), pero el patrón debe coincidir en el principio del string.
import re
print(bool(re.match(r'\d+', 'abc123'))) # False — no digit at start
print(bool(re.search(r'\d+', 'abc123'))) # True — digit found anywhereUsa re.search() cuando quieras encontrar un patrón en cualquier lugar; usa re.match() cuando el patrón deba aparecer al inicio.
re.fullmatch() — Coincidir con el string completo
El patrón debe coincidir con todo el string de principio a fin.
import re
print(bool(re.fullmatch(r'\d{5}', '12345'))) # True — exactly 5 digits
print(bool(re.fullmatch(r'\d{5}', '123456'))) # False — too long
print(bool(re.fullmatch(r'\d{5}', '1234X'))) # False — non-digit presentre.fullmatch() es ideal para la validación de entradas (códigos postales, números de teléfono, etc.).
re.findall() — Todas las coincidencias no solapadas
Devuelve una lista de todas las coincidencias. Si el patrón tiene grupos, devuelve una lista de tuplas.
import re
print(re.findall(r'\d+', 'abc 123 def 456'))
# ['123', '456']
# With a group — returns list of group values
print(re.findall(r'(\w+)@(\w+\.\w+)', '[email protected] [email protected]'))
# [('alice', 'example.com'), ('bob', 'test.org')]re.finditer() — Iterador de objetos de coincidencia
Similar a re.findall(), pero devuelve objetos de coincidencia uno a la vez. Útil para textos grandes o cuando necesitas información de posición.
import re
for m in re.finditer(r'\d+', 'abc 123 def 456'):
print(m.group(), m.start(), m.end())
# 123 4 7
# 456 12 15re.sub() — Reemplazar coincidencias
Reemplaza cada ocurrencia del patrón con un string de reemplazo o el valor de retorno de una función.
import re
text = 'apple banana apple'
print(re.sub(r'apple', 'orange', text))
# 'orange banana orange'
# Limit replacements
print(re.sub(r'apple', 'orange', text, count=1))
# 'orange banana apple'
# Backreferences in replacement — reformat a date
print(re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', '2023-10-05'))
# '05/10/2023're.split() — Dividir por un patrón
Divide el string en cada ocurrencia del patrón.
import re
print(re.split(r',\s*', 'apple, banana, cherry, date'))
# ['apple', 'banana', 'cherry', 'date']
# Limit the number of splits
print(re.split(r',\s*', 'apple, banana, cherry, date', maxsplit=2))
# ['apple', 'banana', 'cherry, date']Compilar patrones con re.compile()
Cuando uses el mismo patrón muchas veces, compílalo una vez con re.compile() para mejorar el rendimiento. El objeto de patrón resultante tiene todos los mismos métodos (findall, search, sub, etc.).
import re
# Compile once
digit_pattern = re.compile(r'\d+')
# Reuse many times
print(digit_pattern.findall('abc 123 def 456')) # ['123', '456']
print(digit_pattern.sub('NUM', 'abc 123 def 456')) # 'abc NUM def NUM'
print(digit_pattern.search('xyz 99')) # <re.Match object ...>La compilación es especialmente valiosa dentro de bucles o funciones que se llaman con frecuencia.
Flags
Los flags modifican el comportamiento de la coincidencia. Pásalos como último argumento a cualquier función de re, o inclúyelos al llamar a re.compile(). Se pueden combinar múltiples flags con |.
| Flag | Forma corta | Efecto |
|---|---|---|
re.IGNORECASE | re.I | Coincidencia sin distinguir mayúsculas/minúsculas |
re.MULTILINE | re.M | ^ y $ coinciden con el inicio/fin de cada línea |
re.DOTALL | re.S | . también coincide con saltos de línea |
re.VERBOSE | re.X | Permite espacios en blanco y comentarios dentro del patrón |
import re
# IGNORECASE
print(re.findall(r'hello', 'Hello HELLO hello', re.IGNORECASE))
# ['Hello', 'HELLO', 'hello']
# MULTILINE — ^ matches the start of each line
text = 'first line\nsecond line\nthird line'
print(re.findall(r'^\w+', text, re.MULTILINE))
# ['first', 'second', 'third']
# DOTALL — . matches newline characters
print(re.findall(r'<.*?>', '<div>\n<p>text</p>\n</div>', re.DOTALL))
# ['<div>', '<p>', '</p>', '</div>']
# VERBOSE — write readable patterns with comments
date_pattern = re.compile(r'''
(?P<year>\d{4}) # four-digit year
-
(?P<month>\d{2}) # two-digit month
-
(?P<day>\d{2}) # two-digit day
''', re.VERBOSE)
print(date_pattern.search('2023-10-05').groupdict())
# {'year': '2023', 'month': '10', 'day': '05'}Escapar entradas del usuario con re.escape()
Si construyes un patrón a partir de texto proporcionado por el usuario, escápalo siempre primero para evitar la interpretación no intencionada de metacaracteres.
import re
user_input = 'hello.world'
# Without escaping, '.' matches any character
# With escaping, '\.' matches a literal dot
safe_pattern = re.escape(user_input)
print(safe_pattern) # 'hello\\.world'
print(bool(re.search(safe_pattern, 'say hello.world'))) # True
print(bool(re.search(safe_pattern, 'say helloXworld'))) # FalseEjemplos prácticos
Validar una dirección de correo electrónico
import re
def is_valid_email(email):
pattern = r'^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$'
return bool(re.fullmatch(pattern, email))
print(is_valid_email('[email protected]')) # True
print(is_valid_email('bad-email@')) # FalseExtraer todas las URLs de un texto
import re
text = 'Visit https://www.example.com or http://test.org for details.'
urls = re.findall(r'https?://[\w./-]+', text)
print(urls)
# ['https://www.example.com', 'http://test.org']Eliminar espacios en blanco adicionales
import re
messy = ' hello world Python '
clean = re.sub(r'\s+', ' ', messy).strip()
print(clean) # 'hello world Python'Analizar una línea de log
import re
log = '2023-10-05 14:32:11 ERROR Failed to connect to database'
pattern = re.compile(
r'(?P<date>\d{4}-\d{2}-\d{2}) '
r'(?P<time>\d{2}:\d{2}:\d{2}) '
r'(?P<level>\w+) '
r'(?P<message>.+)'
)
m = pattern.match(log)
if m:
print(m.group('level')) # 'ERROR'
print(m.group('message')) # 'Failed to connect to database'Errores comunes
re.match() vs re.search() — re.match() solo comprueba el inicio del string. Los nuevos usuarios a menudo esperan que busque en cualquier lugar y se confunden cuando devuelve None.
Olvidar los raw strings — '\d' en un string normal de Python es simplemente 'd' con un escape no reconocido (o un SyntaxWarning en Python 3.12+). Escribe siempre r'\d'.
Las coincidencias codiciosas que capturan demasiado — Si un patrón .* captura más de lo que pretendes, cámbialo a .*? (perezoso).
Caracteres especiales en los strings de reemplazo — En re.sub(), las secuencias con barra invertida como \1 en el reemplazo hacen referencia a grupos capturados. Para incluir una barra invertida literal en el reemplazo, escribe \\.
re.findall() con grupos — Cuando tu patrón contiene grupos, re.findall() devuelve los grupos, no la coincidencia completa. Usa un grupo sin captura (?:...) si quieres la coincidencia completa.
Referencia rápida
| Tarea | Función |
|---|---|
| Encontrar la primera coincidencia | re.search(pattern, text) |
| Encontrar todas las coincidencias | re.findall(pattern, text) |
| Iterar coincidencias | re.finditer(pattern, text) |
| Coincidir al inicio | re.match(pattern, text) |
| Coincidir con el string completo | re.fullmatch(pattern, text) |
| Reemplazar | re.sub(pattern, repl, text) |
| Dividir | re.split(pattern, text) |
| Compilar para reutilizar | re.compile(pattern) |
| Escapar entrada del usuario | re.escape(text) |
Capítulos relacionados
- Python Strings — métodos de string que complementan regex para tareas de texto más simples.
- Modify Strings — métodos integrados como
replace()ysplit()que evitan regex cuando los patrones son fijos. - Python File Handling — leer archivos línea por línea para aplicar regex a gran escala.
- Python Try/Except — manejar errores que surgen cuando los patrones regex se compilan a partir de entradas del usuario.
- Python Functions — encapsular la lógica de regex en funciones reutilizables.