R

ReflexKR

Documentación de Reflex
Expresiones regulares a la vista
Lección 2

Tiempo 0:50–1:40

El coche que se dio a la fuga: clases de caracteres

CASE-002

Grep & Co. Agencia de Detectives de Datos

Expediente

El coche que se dio a la fuga

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:

  • Los dos primeros dígitos eran 12 o 17.
  • La sílaba del medio era 가, 거 o 고.
  • El último dígito era un 7.
  • El coche salió por la salida (OUT).

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 12a3457

Cada 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.

2-1. Corchetes: uno de estos caracteres (5 min)

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
  • Da igual cuántos caracteres pongas entre corchetes: ocupan una sola posición. [가거고] no son tres caracteres, sino uno.
  • El orden no importa. [가거고] y [고거가] son la misma clase.
  • Si el . 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

Cambia la sílaba del medio por [가거고]. 가, 거 y 고 deben pasar; 나 tiene que quedarse fuera.

2-2. Rangos: [0-9], [a-z], [가-힣] (8 min)

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']
  • Puedes encadenar varios rangos. [a-z0-9] es un carácter que sea minúscula o dígito.
  • Los rangos siguen el orden del punto de código (el número Unicode) de cada carácter. Por eso [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 1

Laboratorio de pruebas

re.search(r"", text)

Valor devuelto

Cambia el '가' del medio por [가-힣] para encontrar las cuatro matrículas. Las dos líneas que la cámara leyó mal tienen que quedarse fuera.

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].

2-3. Negación: [^...], todo menos esto (7 min)

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']
  • El ^ 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».
  • El salto de línea tampoco es un dígito, así que [^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

[^0-9] atrapa también el hangul y los espacios. Añade 가-힣 y un espacio dentro de los corchetes para que solo queden los caracteres mal leídos.

2-4. Abreviaturas: \d \w \s (10 min)

Las clases más habituales tienen una forma abreviada: una barra invertida y una letra. En mayúscula, significan lo contrario.

NotaciónSignificadoClase aproximada
\dun dígito[0-9]
\Dun carácter que no es un dígito[^0-9]
\wuna letra, un dígito o un guion bajo (incluido el hangul)más amplia que [a-zA-Z0-9_]
\Wun carácter que no es \w
\sun carácter de espacio en blanco (espacio, tabulador, salto de línea, etc.)más amplia que [ \t\n\r\f\v]
\Sun 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

\D acepta también guiones y letras inglesas. Cambia el \D del medio por [가-힣] para que solo queden las matrículas de verdad.

2-5. Dentro de los corchetes, las reglas cambian (5 min)

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 literal
re.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.

2-6. Unicode y los símbolos repetidos que conviene evitar (5 min)

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']
  • Si quieres exactamente del 0 al 9, usa [0-9] en lugar de \d.
  • El flag 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 2

Hoy 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.

Caso resuelto (10 min)

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.

Ejercicios

  1. Escribe un patrón que encuentre solo los nombres de las puertas del registro, como B1 y B2. ¿En qué se diferencian B[12] y B\d?
  2. ¿Qué encuentra [^\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.
  3. Escribe con abreviaturas un patrón para la forma «dos dígitos:dos dígitos», como la hora 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.

Datos: tu correo electrónico · Finalidad: avisos de nuevas lecciones o de la publicación de este curso · Conservación: hasta que pidas que lo borremos. No lo usamos para nada más ni lo compartimos. Responde a cualquier aviso y lo borraremos enseguida.

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.

Se muestra como texto plano, sin enlaces ni formato.

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
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex