R

ReflexKR

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

Tiempo 1:40–2:30

La guía telefónica rota: cuantificadores

CASE-003

Grep & Co. Agencia de Detectives de Datos

Expediente

La guía telefónica rota

Cliente

La dueña de la floristería «Flor Silvestre», en el mismo callejón

Pruebas

La lista de teléfonos de los clientes habituales, empapada por la lluvia y rota, pasada a texto con OCR. Cada número usa un separador distinto y se han colado cifras que no pintan nada.

Para cerrar el caso

Reunir todos los números de teléfono y todos los códigos postales de la lista.

Llegas por la mañana y la directora tiene en pantalla el patrón de matrícula de ayer. «En vez de escribir \d cuatro veces, se puede escribir "cuatro veces". Hoy aprendes eso». En ese momento se abre la puerta y entra la dueña de la floristería del callejón con un fajo de papeles mojados. «Se me ha empapado la libreta de teléfonos de los clientes de siempre. La escaneé y la pasé a texto, pero ha salido un desastre».

La lista leída con OCR es esta. Cada número usa un separador distinto, y la última línea tiene algo raro.

kim   010-0000-1234   04542
lee   010 0000 9876   04545
park  02-0000-5678    04538
choi  010.0000.4321   04539
yoon  1234-5678-9012

La sintaxis que indica cuántas veces se repite algo se llama cuantificador. Hoy vas a poner orden en esta lista con cuantificadores.

3-1. Exactamente tantas veces: {m} y {m,n} (7 min)

Un cuantificador decide cuántas veces se repite el bloque que tiene justo delante. \d{4} es lo mismo que \d\d\d\d. Acortemos el patrón de matrícula de la lección 2.

re.findall(r"\d{2}[가-힣]\d{4}", "12가3457 123가4567")   # ['12가3457', '23가4567']

Es más corto, pero ya se ve un problema. Las matrículas nuevas, que salieron en 2019, empiezan con tres dígitos. De 123가4567 se ha comido el 1 inicial y solo se ha llevado 23가4567.

{m,n} significa entre m y n veces. Si cambias el principio por \d{2,3}, acepta tanto dos dígitos como tres.

re.findall(r"\d{2,3}[가-힣]\d{4}", "12가3457 123가4567")   # ['12가3457', '123가4567']
  • En {2,3} no se ponen espacios alrededor de la coma. Si hay un espacio, deja de ser un cuantificador (mira el consejo de Dot, más abajo).
  • Los códigos postales coreanos tienen cinco dígitos, así que se escriben \d{5}.

Laboratorio de pruebas

re.search(r"", text)

Valor devuelto

Es el patrón largo de la lección 2. Acórtalo con \d{2,3} y \d{4} para que pasen también las matrículas nuevas de tres dígitos.

3-2. Como mínimo, como máximo: {m,} y {,n} (6 min)

En {m,n} se puede dejar vacío uno de los dos lados.

  • {m,} : m veces o más, sin límite por arriba
  • {,n} : entre 0 y n veces. Como vale 0 veces, también coincide en un sitio donde no hay ningún carácter
re.findall(r"a{2,}", "a aa aaaa")   # ['aa', 'aaaa']
re.findall(r"\d{3,}", "1 22 333 4444")   # ['333', '4444']
re.findall(r"\d{,2}", "123")        # ['12', '3', '']

El '' del final del último resultado es una coincidencia en el final de la cadena, donde hay cero dígitos. Estas coincidencias vacías las veremos con calma en el apartado 3-6.

Si la { no tiene forma de cuantificador, se trata como un carácter normal. a{x} busca los cuatro caracteres a, {, x y }.

re.findall(r"a{x}", "a{x} ax")      # ['a{x}']
re.findall(r"a{2, 3}", "aa a{2, 3}")   # ['a{2, 3}']

El consejo de Dot

Puse un espacio después de la coma, a{2, 3}, y en vez de dar error se puso a buscar otra cosa, tan tranquilo. Si no tiene forma de cuantificador, lo toma como texto normal. Que no dé error no quiere decir que el patrón esté bien. Dentro de las llaves, solo números y comas.

3-3. Los tres de siempre: * + ? (8 min)

Las cantidades más habituales tienen nombre de un solo carácter.

CuantificadorSignificadoEquivale a
*0 veces o más{0,}
+1 vez o más{1,}
?0 o 1 vez (puede estar o no){0,1}
re.findall(r"ab*", "a ab abbb")      # ['a', 'ab', 'abbb']
re.findall(r"ab+", "a ab abbb")      # ['ab', 'abbb']
re.findall(r"ab?", "a ab abbb")      # ['a', 'ab', 'ab']
re.findall(r"colou?r", "color colour colouur")   # ['color', 'colour']

? va como anillo al dedo para «un carácter que puede estar o no». En la lista, los móviles usan separadores distintos: guion, espacio o punto. Si escribes la posición del separador como [-. ]?, vale cualquiera de los tres, o ninguno.

re.findall(r"010[-. ]?\d{4}[-. ]?\d{4}", phonebook)
# ['010-0000-1234', '010 0000 9876', '010.0000.4321']

Como viste en la lección 2, dentro de los corchetes el . es un punto de verdad, y el - es literal porque va el primero.

Laboratorio de pruebas

re.search(r"", text)

Valor devuelto

Este patrón solo acepta guiones. Cambia la posición del separador por [-. ]? para que acepte también espacios, puntos y ningún separador.

3-4. El cuantificador solo afecta al bloque de justo delante (5 min)

El cuantificador se aplica solo al bloque que tiene justo delante. Un bloque es un carácter, una clase entre corchetes o una abreviatura como \d. Por eso ab+ no es «ab varias veces», sino «una a seguida de varias b».

re.findall(r"ab+", "abbb abab")      # ['abbb', 'ab', 'ab']
re.fullmatch(r"ab+", "abab")         # None
re.fullmatch(r"(ab)+", "abab")       # <re.Match object; span=(0, 4), match='abab'>

Para repetir varios caracteres a la vez, agrúpalos entre paréntesis y forma un solo bloque. (ab)+ es «ab una vez o más». Los paréntesis tienen otra función importante además de agrupar, pero esa la verás en la lección 5.

re.fullmatch solo coincide si la cadena entera encaja con el patrón. Las diferencias las veremos en detalle en la lección 4.

Laboratorio de pruebas

re.search(r"", text)

Valor devuelto

ha+ solo repite la a. Agrupa con paréntesis para repetir ha entero. (Si cambias a findall, el resultado se ve raro; el porqué lo verás en la lección 5).

3-5. Los cuantificadores son glotones (4 min)

Por defecto, los cuantificadores son codiciosos (greedy): dentro de lo permitido, se llevan todo lo que pueden.

re.search(r"\d+", "tel 0100000")     # <re.Match object; span=(4, 11), match='0100000'>
re.search(r"\d{2,4}", "123456")      # <re.Match object; span=(0, 4), match='1234'>
re.findall(r"\d{2,4}", "123456")     # ['1234', '56']

\d{2,4} también aceptaría dos dígitos, pero primero se lleva cuatro. Solo si lo que viene después falla, va devolviendo de uno en uno. Este «devolver y volver a intentarlo» se llama backtracking, y en la lección 8 será lo que deje la agencia paralizada. El cuantificador perezoso +?, que se lleva lo menos posible, también lo verás entonces.

3-6. Encontrarlos todos: findall y finditer (10 min)

Ahora vamos a reunir lo que encontremos en toda la lista. Supongamos que la lista de arriba está en una cadena llamada phonebook.

re.findall devuelve el texto que coincide como una lista de cadenas. Así se comporta cuando el patrón no tiene paréntesis; qué pasa cuando los tiene lo veremos en la lección 5.

re.findall(r"\d{5}", phonebook)     # ['04542', '04545', '04538', '04539']

re.finditer devuelve un iterador que va entregando objetos Match uno a uno. Se usa cuando también necesitas la posición (span).

for m in re.finditer(r"\d{5}", phonebook):
    print(m.group(), m.span())
# 04542 (22, 27)
# 04545 (50, 55)
# 04538 (78, 83)
# 04539 (106, 111)
  • El iterador busca cada coincidencia cuando hace falta, así que ahorra memoria incluso con cadenas muy largas.
  • Un iterador se gasta al recorrerlo entero. Para volver a recorrerlo, llama otra vez a re.finditer.

Aquí hay que tener cuidado con las coincidencias vacías. *, ? y {,n} permiten cero repeticiones, así que también tienen éxito en sitios donde no hay ni un carácter. findall mete esas coincidencias vacías en el resultado como ''.

re.findall(r"\d*", "a12b")    # ['', '12', '', '']
re.findall(r"\d+", "a12b")    # ['12']

Ha salido una coincidencia vacía delante de la a, luego 12, otra vacía delante de la b y otra al final de la cadena. Cuando quieras reunir algo, usa + o {1,}, que no permiten cero repeticiones.

Laboratorio de pruebas

re.search(r"", text)

Valor devuelto

\d* deja una cadena vacía en cada sitio donde no hay dígitos. Cambia * por + para reunir solo los bloques de dígitos.

Caso resuelto (10 min)

Un número de teléfono tiene un prefijo de 2 o 3 dígitos, un bloque central de 3 o 4 y un bloque final de 4. El separador puede estar o no. El código postal tiene cinco dígitos.

phones = re.findall(r"\d{2,3}[-. ]?\d{3,4}[-. ]?\d{4}", phonebook)
print(phones)
# ['010-0000-1234', '010 0000 9876', '02-0000-5678', '010.0000.4321', '234-5678-9012']

postcodes = re.findall(r"\d{5}", phonebook)
print(postcodes)   # ['04542', '04545', '04538', '04539']

Hemos reunido cuatro teléfonos y cuatro códigos postales. Pero ¿qué es ese quinto número, 234-5678-9012? La florista mira la pantalla y niega con la cabeza. «Eso no es un teléfono. Es una nota con mi número de tarjeta».

re.search(r"\d{2,3}[-. ]?\d{3,4}[-. ]?\d{4}", "yoon  1234-5678-9012")
# <re.Match object; span=(7, 20), match='234-5678-9012'>

El patrón se ha saltado el primer carácter de 1234, el 1, y ha empezado a coincidir en la posición 7, en mitad de un número. Es el mismo accidente que el 23가4567 del apartado 3-1.

Laboratorio de pruebas

re.search(r"", text)

Valor devuelto

Es el patrón del caso resuelto. Fíjate en que el resaltado de la última línea empieza dejando fuera el 1. Lo arreglaremos en la lección 4.

Informe de caso cerrado — Se recuperan de la lista OCR 4 teléfonos y 4 códigos postales, y se entregan a la dueña de la floristería. Queda sin resolver el problema de que parte de un número de tarjeta se tome por un teléfono.

La directora se queda un buen rato mirando la última línea. «Ha empezado en mitad del número. Este patrón no sabe dónde empieza ni dónde acaba un número». En la próxima lección aprenderás a escribir esos límites en el patrón.

Ejercicios

  1. Intenta adivinar qué encuentra \d{2,3}[-. ]?\d{3,4}[-. ]?\d{4} en 01000001234 (un número sin separadores) y luego ejecútalo.
  2. Adivina el resultado de re.findall(r"\d?", "a1"). ¿Cuántas cadenas vacías habrá?
  3. Usa re.finditer para encontrar los teléfonos de la lista e imprime, línea a línea, cada número con su posición inicial.

La libreta de la directora

  • {m} es exactamente m veces; {m,n}, de m a n; {m,}, m o más; {,n}, de 0 a n. Nada de espacios junto a la coma.

  • * es 0 veces o más; +, 1 vez o más; ?, 0 o 1 vez.

  • El cuantificador solo afecta al bloque de justo delante. ab+ y (ab)+ no son lo mismo.

  • Los cuantificadores son codiciosos: se llevan todo lo que pueden.

  • re.findall devuelve una lista de cadenas; re.finditer, un iterador de Match.

  • Los patrones que permiten 0 repeticiones producen coincidencias vacías ''. Para reunir cosas, usa +.

← Anterior

Lección 2 · El coche que se dio a la fuga: clases de caracteres

Siguiente →

Lección 4 · Los registros falsos: anclas y validación

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