Expresiones regulares a la vista
·
Resumen del curso
1
El pago desaparecido: buscar el texto tal cual
2
El coche que se dio a la fuga: clases de caracteres
3
La guía telefónica rota: cuantificadores
4
Los registros falsos: anclas y validación
5
Fechas por todas partes: grupos y alternativas
6
El acceso de las 3 de la madrugada: grupos con nombre y objetos Match
7
La víspera de la noticia: sustitución y división
8
El desafío del Backtracker: codicioso, perezoso y posesivo
9
Las reglas de la caja fuerte: lookaround
10
El mismo modus operandi: retrorreferencias y condicionales
11
La regex ilegible: flags y compilación
12
El Backtracker, desenmascarado: repaso y un tokenizador
Tiempo 0:50–1:40
CASE-002
Grep & Co. Agencia de Detectives de Datos
Expediente
Cliente
El inspector Kang, de la comisaría del barrio
Pruebas
La declaración de un testigo y el registro de entradas y salidas de un aparcamiento subterráneo. El testigo solo vio parte de la matrícula.
Para cerrar el caso
Sacar todas las matrículas del registro del aparcamiento y reducir los sospechosos a un solo coche que encaje con la declaración.
Cae la tarde. El inspector Kang empuja la puerta de la agencia sacudiendo un paraguas empapado. «Anoche, sobre las 22:45, un atropello con fuga en la salida de un aparcamiento subterráneo. El testigo no llegó a ver la matrícula entera». La directora no levanta las manos del teclado. «Si no la vio entera, se escribe como patrón lo que sí vio. Las pruebas dejan patrones».
Las matrículas coreanas tienen esta forma: dos dígitos (o tres, en las más nuevas), una sílaba en hangul (el alfabeto coreano) y cuatro dígitos, como 12가3457. La declaración del testigo, tal como quedó en el acta:
- 앞 두 자리는 12 아니면 17이었어요.
- 가운데 한글은 가, 거, 고 중 하나였어요.
- 마지막 숫자는 7이었어요.
- 차는 출구(OUT) 쪽으로 빠져나갔어요.Es decir:
Y este es el registro de entradas y salidas del aparcamiento. La cámara, de vez en cuando, lee mal las matrículas.
2026-10-19 22:41 IN B1 12가3457
2026-10-19 22:43 IN B1 34나1207
2026-10-19 22:47 OUT B2 17거3407
2026-10-19 22:52 IN B1 12-3457
2026-10-19 22:58 OUT B2 56다9081
2026-10-19 23:05 IN B1 12a3457Cada línea de la declaración es una condición del tipo «en esta posición va uno de estos». Las clases de caracteres, lo que aprendes hoy, son justo la sintaxis para escribir esas condiciones.
Los corchetes [...] significan un solo carácter, cualquiera de los que hay dentro. El testigo dijo «uno de 가, 거 o 고», así que esa posición se escribe [가거고].
import re
re.findall("12[가거고]3457", "12가3457 12거3457 12나3457") # ['12가3457', '12거3457']
re.findall(r"[ab]c", "ac bc cc") # ['ac', 'bc']
re.search(r"[abc]", "xyz") # None[가거고] no son tres caracteres, sino uno.[가거고] y [고거가] son la misma clase.. de la lección 1 era «un carácter cualquiera», los corchetes son «uno de los que yo elija».Laboratorio de pruebas
re.search(r"", text)
Valor devuelto
Escribir los diez dígitos uno por uno, como [0123456789], es un rollo. Dentro de los corchetes, un - entre dos caracteres forma un rango. [0-9] va del 0 al 9, y [a-z] son todas las letras minúsculas del alfabeto inglés.
re.findall(r"[0-9]", "B1") # ['1']
re.findall(r"[a-z0-9]", "B1 b2") # ['1', 'b', '2']
re.findall(r"[a-zA-Z]", "a1Z") # ['a', 'Z'][a-z0-9] es un carácter que sea minúscula o dígito.[a-z] no incluye las mayúsculas.En Unicode, las sílabas completas del hangul van seguidas, del 가 (U+AC00) al 힣 (U+D7A3): 11 172 caracteres en total. Así que un solo [가-힣] atrapa cualquier sílaba hangul completa.
print(hex(ord("가")), hex(ord("힣"))) # 0xac00 0xd7a3
re.findall(r"[가-힣]", "ㄱ ㅏ 가") # ['가']Las letras sueltas como ㄱ o ㅏ viven en otra zona de Unicode, así que [가-힣] no las incluye. La sílaba del medio de una matrícula siempre es una sílaba completa, así que este rango basta.
Si escribes un rango al revés, el error salta en cuanto se crea el patrón.
re.compile("[a-Z]") # re.PatternError: bad character range a-Z at position 1Laboratorio de pruebas
re.search(r"", text)
Valor devuelto
El consejo de Dot
Los rangos solo se escriben del punto de código pequeño al grande. Si pones [a-Z] para pillar mayúsculas y minúsculas de una vez, te da error: la a es el 97 y la Z el 90, o sea, al revés. Si quieres las dos, usa dos rangos, como [a-zA-Z].
Si pones ^ justo al principio, dentro de los corchetes, el significado se invierte. [^0-9] es un carácter que no sea un dígito.
re.findall(r"[^0-9]", "12가34") # ['가']
re.findall(r"[0-9^]", "2^3") # ['2', '^', '3']
re.findall(r"[^0-9]", "1\n2") # ['\n']^ solo niega cuando va el primero. Si va detrás, como en [0-9^], es simplemente el carácter ^.[^0-9] también ocupa un carácter. No es «un sitio donde no hay dígitos», sino «un carácter que no es un dígito».[^0-9] también lo atrapa.La cámara del aparcamiento a veces confunde dígitos con letras: lee el 1 como I, el 5 como S o el 2 como Z. Si buscas los caracteres que no son ni dígitos, ni hangul, ni espacios, quedan al descubierto los que se leyeron mal.
El consejo de Dot
Yo no puedo cruzar la ventana (el salto de línea), pero [^0-9] sí: un salto de línea también es «un carácter que no es un dígito». Cuando uses [^...] en un texto de varias líneas, comprueba siempre si te está atrapando también los saltos de línea.
Laboratorio de pruebas
re.search(r"", text)
Valor devuelto
Las clases más habituales tienen una forma abreviada: una barra invertida y una letra. En mayúscula, significan lo contrario.
| Notación | Significado | Clase aproximada |
|---|---|---|
\d | un dígito | [0-9] |
\D | un carácter que no es un dígito | [^0-9] |
\w | una letra, un dígito o un guion bajo (incluido el hangul) | más amplia que [a-zA-Z0-9_] |
\W | un carácter que no es \w | |
\s | un carácter de espacio en blanco (espacio, tabulador, salto de línea, etc.) | más amplia que [ \t\n\r\f\v] |
\S | un carácter que no es espacio en blanco |
re.findall(r"\d", "B1 12가") # ['1', '1', '2']
re.findall(r"\D", "12가-3") # ['가', '-']
re.findall(r"\w", "a_1 가!") # ['a', '_', '1', '가']
re.findall(r"\W", "a_1 가!") # [' ', '!']
re.findall(r"\s", "a b\tc\nd") # [' ', '\t', '\n']
re.findall(r"\S", "a b\tc") # ['a', 'b', 'c']Las abreviaturas se pueden mezclar con otros caracteres dentro de los corchetes. [\d,.] es un carácter que sea «un dígito, una coma o un punto».
re.findall(r"\d[\d,.]\d", "1,2 3.4 5-6 789") # ['1,2', '3.4', '789']Ahora escribamos la matrícula con abreviaturas. Usar \D (lo que no es un dígito) para la posición del medio parece cómodo, pero entonces también pasan 12-3457 y 12a3457, que la cámara leyó mal. En el medio no va «cualquier cosa que no sea un dígito», sino «una sílaba hangul».
Laboratorio de pruebas
re.search(r"", text)
Valor devuelto
Dentro de los corchetes, casi todos los metacaracteres pierden su poder. [.*+?] es simplemente un carácter: punto, asterisco, signo más o signo de interrogación. No hace falta ponerles barra invertida.
Dentro de los corchetes solo quedan cuatro caracteres especiales:
\ : escapes y abreviaturas como \d] : el final de la clase. Pero si va el primero, es un carácter literal^ : negación si va el primero- : rango. Pero si va el primero o el último, es un carácter literalre.findall(r"[.*+?]", "a.b*c+d?") # ['.', '*', '+', '?']
re.findall(r"[]a]", "a]b") # ['a', ']']
re.findall(r"[-+]", "3-2+1") # ['-', '+']
re.findall(r"[a\-z]", "a-mz") # ['a', '-', 'z']
re.findall(r"[\]\\]", r"a]b\c") # [']', '\\'][a\-z] no es un rango, sino tres caracteres: a, - y z. Por eso la m no se atrapa. Si te lías con las reglas de posición, ponles barra invertida y listo: se lee mejor.
En Python, los patrones str son Unicode por defecto. Por eso las abreviaturas abarcan más de lo que parece. Ya has visto que \w atrapa el hangul. \d también considera dígito el ٣ arábigo-índico (U+0663, el número 3). Y \s atrapa el espacio de ancho completo (U+3000), muy común en los textos coreanos.
re.findall(r"\d", "3٣") # ['3', '٣']
print(int("٣")) # 3
re.findall(r"\s", "a\u3000b") # ['\u3000'][0-9] en lugar de \d.re.ASCII, que limita las abreviaturas al rango ASCII, lo veremos en la lección 11.Por último: si dentro de los corchetes repites un símbolo, como [[, --, &&, ~~ o ||, salta un FutureWarning. Python reserva esas combinaciones para, en el futuro, hacer operaciones de conjuntos entre clases (unión, intersección, etc.).
import warnings
warnings.simplefilter("always")
re.compile(r"[[a]") # FutureWarning: Possible nested set at position 1
re.compile(r"[a&&b]") # FutureWarning: Possible set intersection at position 2Hoy funcionan, pero su significado podría cambiar más adelante. Si de verdad necesitas esos caracteres, escapa uno de ellos: [\[a], [a&\&b]. Lo repasaremos en la lección 12.
Supongamos que el registro del aparcamiento está en una cadena llamada parking. Primero sacamos todas las matrículas de verdad. Las dos líneas que la cámara leyó mal se quedan fuera.
plates = re.findall(r"\d\d[가-힣]\d\d\d\d", parking)
print(plates) # ['12가3457', '34나1207', '17거3407', '56다9081']Ahora pasamos la declaración del testigo a clases, posición por posición. Los dos primeros dígitos son 1[27], el del medio [가거고] y el último 7.
print(re.findall(r"1[27][가거고]\d\d\d7", parking)) # ['12가3457', '17거3407']
print(re.findall(r"OUT B\d 1[27][가거고]\d\d\d7", parking)) # ['OUT B2 17거3407']Hay dos candidatos, pero 12가3457 entró antes del accidente y no hay registro de que saliera. El único coche que se fue por la salida dos minutos después del atropello es 17거3407.
Informe de caso cerrado — El 17거3407, que salió por la salida B2 a las 22:47, coincide en todo con la declaración del testigo. Se pide al inspector Kang que consulte los datos del vehículo.
Cuando el inspector Kang se marcha, la directora señala con el dedo el patrón en la pantalla. «\d\d[가-힣]\d\d\d\d. Has escrito lo mismo cuatro veces. Y además, hoy en día hay matrículas que empiezan con tres dígitos». En la próxima lección aprenderás a acortar este patrón tan largo.
B1 y B2. ¿En qué se diferencian B[12] y B\d?[^\d\s]? Intenta adivinarlo con el registro de la cámara de arriba, 12가3457 I2가3457 12가34S7 1Z거3407, y luego ejecútalo.22:41.La libreta de la directora
[abc] es un carácter: a, b o c. Pongas cuantos pongas, ocupa una sola posición.
Los rangos, como [a-z0-9], siguen el orden de los puntos de código. Las sílabas hangul son [가-힣]. [a-Z] da error.
[^...] solo niega si el ^ va el primero. También atrapa el salto de línea.
\d \w \s y sus contrarios \D \W \S. Se pueden mezclar dentro de una clase, como en [\d,.].
Dentro de una clase, . * + ? son caracteres literales. Solo son especiales \ ] ^ -
En los patrones str, \d \w \s abarcan todo Unicode. Si solo quieres del 0 al 9, [0-9].
← Anterior
Lección 1 · El pago desaparecido: buscar el texto tal cual
Siguiente →
Lección 3 · La guía telefónica rota: cuantificadores
Recibe avisos de nuevas lecciones
Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.
R
ReflexKR
Aprende Python y Reflex haciendo. Todos los ejemplos se ejecutaron y verificaron en Reflex 0.10.0.
ReflexKR es gratis y sin banners publicitarios. Si te ayudó, puedes apoyarlo con un café. ☕ Invítanos un café
Aviso de afiliados© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Preguntas y respuestas
Pregunta lo que quieras sobre esta lección. Aquí se reúnen las preguntas de todas las versiones de idioma de esta página.