Expresiones regulares
En resumen: Una expresión regular (regex) es un patrón que describe cómo es un texto: con ella compruebas, buscas, reemplazas y divides texto en lugar de escribir muchos ifs.
Problema
Diez líneas para una fecha
El formulario de reservas pide una fecha con la forma dd.mm.aaaa. La compruebas carácter por carácter:
def is_date(s): if len(s) != 10: return False for i in range(len(s)): c = s[i] if i == 2 or i == 5: if c != ".": return False elif c < "0" or c > "9": return False return True
print(is_date("05.03.2026"))print(is_date("5.3.2026"))print(is_date("05/03/2026"))TrueFalseFalseDiez líneas, y la función solo comprueba el formato: dos dígitos, un punto, dos dígitos, un punto, cuatro dígitos. Si además quieres aceptar / en lugar del punto, o un día de una sola cifra, añadirás otras tantas líneas. Un número de teléfono, un email o una matrícula te costarán otra función como esta cada uno.
La idea
Un patrón en lugar de código
Describes cómo es el texto, y Python hace la comprobación:
import re
def is_date(s): return re.fullmatch(r"\d{2}\.\d{2}\.\d{4}", s) is not None
print(is_date("05.03.2026"))print(is_date("5.3.2026"))print(is_date("05/03/2026"))TrueFalseFalseEl patrón se lee de izquierda a derecha: \d es un dígito, {2} significa «dos veces», \. es un punto. Un patrón así se llama expresión regular (regular expression, regex).
Las expresiones regulares están en el módulo re, así que el archivo empieza con import re. re.fullmatch(patrón, texto) comprueba si todo el texto encaja con el patrón, del primer carácter al último. Si encaja, devuelve la coincidencia. Si no, devuelve None. Por eso is not None da True o False.
¿Por qué hay una r delante de las comillas? La barra invertida también es especial para una cadena normal de Python. Con la r, la cadena es cruda (raw), y \ en ella es solo \. Lo veremos en detalle dentro de un momento. Por ahora, escribe cada patrón con r.
Míralo
Cómo busca el patrón
re.sub no comprueba todo el texto. Busca los trozos que encajan con el patrón y los reemplaza. Así recorre la línea:
import re
line = "120 € el 05.03.2026, vence 15.04.2026"masked = re.sub(r"\d{2}\.\d{2}\.\d{4}", "[fecha]", line)print(masked)Paso a paso, en palabras
- La línea es una secuencia de caracteres, cada uno con su posición.
- Desde la posición 0: «1» y «2» son dígitos, pero después viene «0» en lugar de un punto. El intento falla.
- Un intento nuevo desde cada carácter siguiente: «20 », «0 », « €»… todos fallan.
- Desde la posición 9 encajan las diez partes: la primera coincidencia.
- La búsqueda sigue después de la coincidencia, desde la coma, y encuentra la segunda.
- re.sub reemplaza las dos coincidencias por «[fecha]».
El patrón se prueba desde cada posición, una tras otra. En cuanto una parte no coincide, el intento falla y empieza uno nuevo desde el carácter siguiente. Por eso 120 no pasó: dos dígitos, y después un 0 en lugar de un punto. Cuando encuentra una coincidencia, la búsqueda sigue después de ella.
La idea
Clases de caracteres
Entre corchetes enumeras qué caracteres se permiten en una posición. Una matrícula de coche son una o dos letras, cuatro dígitos y dos letras:
import re
plates = ["CA1234BX", "B7788KK", "CA12345BX", "ca1234bx"]for plate in plates: print(plate, re.fullmatch(r"[A-Z]{1,2}\d{4}[A-Z]{2}", plate) is not None)CA1234BX TrueB7788KK TrueCA12345BX Falseca1234bx False| Patrón | Coincide con un carácter |
|---|---|
[ABE] |
A, B o E |
[0-9], [A-Z] |
del rango: un dígito, una letra latina mayúscula |
[^0-9] |
cualquier cosa excepto un dígito (^ al principio de los corchetes) |
\d |
un dígito, lo mismo que [0-9] |
\s |
un espacio, un tabulador, un salto de línea |
\w |
una letra de cualquier idioma, un dígito o _ |
. |
cualquier carácter (excepto el salto de línea) |
Las mayúsculas invierten el significado: \D es «no un dígito», \S es «no un espacio», \W es «no \w».
En Python \w también conoce letras como «ü» y «é». Con la bandera re.ASCII (el tercer argumento de fullmatch) pasa a ser solo letras latinas, como en Java y JavaScript:
import re
print(re.fullmatch(r"\w+", "Sofia") is not None)print(re.fullmatch(r"\w+", "Zürich") is not None)print(re.fullmatch(r"\w+", "Zürich", re.ASCII) is not None)print(re.fullmatch(r"[A-Za-zü]+", "Zürich") is not None)TrueTrueFalseTrueMás precisamente, para curiosos
[A-Z]es un rango según los códigos de los caracteres. Contiene solo las 26 letras latinas, sin «É», «Ñ» ni «Ü», que están en otro lugar de la tabla. Para un texto en inglés basta con[A-Za-z]. Si también necesitas letras con acento, añádelas:[A-Za-zÉéÑñÜü].- Las matrículas búlgaras de verdad usan solo 12 letras, las que se ven igual en cirílico y en latino:
[ABEKMHOPCTYX].
Pruébalo
El punto
Alguien olvidó la barra invertida delante de los puntos:
¿Qué imprimirá el programa?
import re
pattern = r"\d{2}.\d{2}.\d{4}"print(re.fullmatch(pattern, "05.03.2026") is not None)print(re.fullmatch(pattern, "05-03-2026") is not None)print(re.fullmatch(pattern, "05.03.26") is not None)Sin \, el punto significa «cualquier carácter», así que el guion también pasa, y 05-03-2026 da verdadero. El tercer texto tiene solo dos dígitos para el año, y el patrón pide cuatro. La comprobación parece funcionar hasta que llega una entrada incorrecta. Por eso prueba el patrón también con textos que no deben pasar.
La idea
Cuántas veces
Después de un carácter o de una clase dices cuántas veces puede repetirse. Esto es un cuantificador:
| Cuantificador | Significa |
|---|---|
? |
cero o una vez (puede estar o no) |
* |
cero o más |
+ |
una o más |
{4} |
exactamente 4 |
{1,2} |
de 1 a 2 |
{2,} |
2 o más |
Un número de móvil búlgaro empieza con 0 o +359, después 87, 88 u 89, y 7 dígitos más:
import re
mobile = r"(\+359|0)8[789]\d{7}"print(re.fullmatch(mobile, "0888123456") is not None)print(re.fullmatch(mobile, "+359877654321") is not None)print(re.fullmatch(mobile, "0888 123 456") is not None)print(re.fullmatch(mobile, "088812345") is not None)TrueTrueFalseFalse| significa «o». Los paréntesis mantienen \+359|0 juntos: sin ellos, la «o» partiría todo el patrón en dos. Los paréntesis hacen más cosas, que veremos en la próxima lección. El + del principio lleva \, porque si no, es un cuantificador.
Un cuantificador también funciona sobre un grupo entero entre paréntesis. Aquí tienes un precio con parte decimal o sin ella:
import re
price = r"\d+([.,]\d{1,2})? €"print(re.fullmatch(price, "12 €") is not None)print(re.fullmatch(price, "12.5 €") is not None)print(re.fullmatch(price, "12,50 €") is not None)print(re.fullmatch(price, ".50 €") is not None)TrueTrueTrueFalse([.,]\d{1,2})? significa «un punto o una coma y uno o dos dígitos», y todo este bloque puede faltar.
La idea
Cadena cruda: r"…"
El patrón pasa por dos capas. Primero Python lee la cadena entre comillas, después re lee el patrón.
En una cadena normal, \ empieza un carácter especial: \n es un salto de línea, \" es una comilla. \d no es uno de ellos. Python 3.12 te avisa, pero deja \d tal cual, y el programa funciona:
import re
date = "05.03.2026"print(re.fullmatch("\d{2}\.\d{2}\.\d{4}", date) is not None)/shop/date.py:4: SyntaxWarning: invalid escape sequence '\d' print(re.fullmatch("\d{2}\.\d{2}\.\d{4}", date) is not None)TrueFunciona por suerte. \b, por ejemplo, sí es un carácter especial en una cadena («retroceso», backspace), y un patrón con él pero sin r busca otra cosa sin decir nada. Lo verás dentro de un momento. Por eso los patrones se escriben como cadenas crudas: con r delante de las comillas, \ es solo \:
import re
date = "05.03.2026"print(re.fullmatch(r"\d{2}\.\d{2}\.\d{4}", date) is not None)print(re.fullmatch(r"C:\\Temp", "C:\\Temp") is not None)TrueTrue| Quieres en el patrón | Escribes en Python | Sin r |
|---|---|---|
\d (un dígito) |
r"\d" |
"\\d" |
\. (un punto de verdad) |
r"\." |
"\\." |
\\ (una barra invertida de verdad) |
r"\\" |
"\\\\" |
Los caracteres que necesitan \ cuando los quieres tal cual son . * + ? ( ) [ ] { } | ^ $ \.
Pruébalo
Encuentra el error
Traceback (most recent call last): File "/shop/price.py", line 5, in <module> euros = int(parts[0]) ^^^^^^^^^^^^^ValueError: invalid literal for int() with base 10: ''Elige la línea con el error.
re.split recibe una expresión regular. "." es «cualquier carácter», así que cada carácter es un separador y entre ellos no hay nada: las partes son seis cadenas vacías. El error aparece solo en la línea siguiente, cuando int recibe una cadena vacía. La corrección es re.split(r"\.", price). Pero aquí no necesitas ningún patrón: price.split(".") (el método de las cadenas) busca texto normal.price = "12.50"print(price.split("."))['12', '50']La idea
Reemplazar y dividir con un patrón
re.sub(patrón, reemplazo, texto) y re.split(patrón, texto) trabajan con un patrón. Así limpias y separas un texto que la gente escribe como le da la gana:
import re
address = "Envío: calle Mayor 12, portal B"print(re.sub(r"\s+", " ", address))items = re.split(r"[,;\s]+", "pan, leche;café té")print(f"{len(items)}: " + " | ".join(items))print(re.sub(r"(?i)summer\d+", "***", "Código SUMMER10, código summer25"))Envío: calle Mayor 12, portal B4: pan | leche | café | téCódigo ***, código ***\s+es «uno o más espacios»: cada grupo de espacios se convierte en uno solo.[,;\s]+es «comas, puntos y comas o espacios, los que haya»: el separador puede ser cualquiera de ellos.
(?i)al principio es una bandera (flag): el patrón no distingue entre mayúsculas y minúsculas.
El método de las cadenas replace también reemplaza todas las apariciones, pero busca texto normal, no un patrón. Si no necesitas un patrón, úsalo: no tienes que pensar en puntos ni en barras invertidas.
\b es un límite de palabra: el lugar entre una letra y algo que no es una letra. Con él reemplazas solo palabras completas. En Python \b conoce letras como «é», pero el patrón tiene que ser una cadena cruda:
import re
text = "éxito y éxitos"print(re.sub("éxito", "triunfo", text))print(re.sub("\béxito\b", "triunfo", text))print(re.sub(r"\béxito\b", "triunfo", text))triunfo y triunfoséxito y éxitostriunfo y éxitosSin \b, «éxitos» se convierte en «triunfos». La segunda línea no tiene r: ahí \b es el carácter de retroceso, no un límite, así que no se reemplaza nada y no hay ningún error. Solo con la cadena cruda de la tercera línea se reemplaza únicamente la palabra «éxito».
Más precisamente, para curiosos
\Bes lo contrario de\b: un lugar dentro de una palabra.r"\Bé"encuentra la «é» solo cuando no está al principio de una palabra: en «café», pero no en «éxito».^es el principio del texto, y$es el final:re.sub(r"^0+", "", text)quita los ceros iniciales. Confullmatchno los necesitas, porque de todos modos mira todo el texto.- Las banderas también se pueden dar como constantes:
re.IGNORECASEes lo mismo que(?i), yre.ASCIIque(?a). Se pasan como último argumento:re.sub(patrón, reemplazo, texto, flags=re.IGNORECASE). re.split("|", "a|b")es una trampa como la del punto:|es «o», y el patrón está vacío, así que divide entre cada dos caracteres. El método de las cadenas"a|b".split("|")busca texto normal y no tiene este problema.
import re
print(re.sub(r"\Bé", "e", "café éxito"))print(re.fullmatch(r"(?a)\w+", "Zürich"), re.fullmatch(r"(?i)éxito", "ÉXITO") is not None)print(re.split("|", "a|b"))print("a|b".split("|"))cafe éxitoNone True['', 'a', '|', 'b', '']['a', 'b']Pruébalo
Código postal
Un código postal búlgaro tiene exactamente cuatro dígitos. ¿Qué falta para que el programa imprima True para "1000" y False para "100" o "10000"?
import re
code = "1000"
print(re.fullmatch(r"", code) is not None)\d{4}: exactamente cuatro dígitos. Como fullmatch comprueba todo el texto, tres o cinco dígitos no pasan.Ejercicio
Comprobaciones con un patrón
Resuelve los ejercicios en tu editor. Lee líneas mientras haya: for line in sys.stdin:, con import sys arriba. Cada línea termina en "\n", así que quítalo con strip() o rstrip("\n"). En la lección «Archivos» leerás las mismas líneas desde un archivo.
1. Fecha. Comprueba si cada fecha tiene la forma dd/mm/aaaa. El día va de 01 a 31, y el mes de 01 a 12. Pista: el día es 0[1-9] o [12]\d, o 3[01].
01/01/202032/01/202015/05/202101/01/2020 - Válida32/01/2020 - No válida15/05/2021 - Válida2. Número de móvil. La gente escribe los números con espacios y guiones. Quítalos con re.sub y comprueba lo que queda con el patrón de número de móvil de la lección.
0888 123 456+359 87 765 432102 812 34560899-12-3450888 123 456 - válido+359 87 765 4321 - válido02 812 3456 - no válido0899-12-345 - no válido3. Email. Comprueba si cada línea parece un email: una o más letras latinas, dígitos, ., + o -, después @, y después un nombre de dominio con al menos un punto (empresa.es, mail.empresa.es). Aquí \w también dejaría pasar letras como «ü»: enumera las letras latinas con [A-Za-z0-9_].
lucia.garcia@empresa.eslucia@empresa@empresa.eslucia garcia@empresa.eslucia.garcia@empresa.es - sílucia@empresa - no@empresa.es - nolucia garcia@empresa.es - no4. Lista de la compra. Los productos están separados por comas, puntos y comas, barras verticales o espacios, a veces varios a la vez. Imprímelos numerados.
pan, leche;café té|queso1. pan2. leche3. café4. té5. quesoMás precisamente, para curiosos
- El patrón de la fecha también acepta
31/02/2026. Una regex no puede saber de forma razonable si el día existe en ese mes. Para eso está el módulodatetime, y el patrón es solo el primer filtro:
import datetime
try: datetime.date(2026, 2, 31)except ValueError as e: print(e)day is out of range for month- La regla completa de una dirección de email (RFC 5322) permite comillas, comentarios y espacios en ciertos lugares. Un patrón que lo cubra todo tiene cientos de caracteres, y nadie lo mantiene. La comprobación de verdad es enviar un email con un enlace de confirmación.
Más ejercicios
- Contraseña. Comprueba si la contraseña tiene al menos 8 caracteres y al menos un dígito. Hazlo con dos
re.fullmatch:r".{8,}"yr".*\d.*". - Hora. Comprueba una hora con la forma
hh:mm, de00:00a23:59. - Solo dígitos. De
"Tel.: +359 (88) 812-34-56"deja solo los dígitos y el+, con un solore.suby[^...].
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Funciones · Parámetros y return · Excepciones · Cadenas
- Continúa en: Expresiones regulares: grupos y reemplazo · Datos en archivos · Listas por comprensión y generadores · Programación en red
- Ejercicios sobre el tema: Texto y archivos