Los bucles de la lección anterior tienen una virtud y un defecto, y son el mismo: son todo o nada. Un while insiste hasta que la condición se vuelve falsa, cueste lo que cueste; un for recorre su secuencia entera aunque en la primera vuelta ya sepa la respuesta. Si Marta se equivoca cinco veces seguidas al escribir la prioridad, TareaFácil la interroga cinco veces sin la menor compasión: no sabe rendirse.
Esta lección añade el control fino del flujo: instrucciones que cortan un bucle a mitad de camino, que se saltan una vuelta concreta, que distinguen entre «terminé de buscar y lo encontré» y «terminé de buscar y no estaba», y que combinan dos bucles uno dentro de otro. Cerramos con match, la sentencia que Python incorporó en su versión 3.10 para clasificar valores con más elegancia que una cadena de elif. Son herramientas potentes y, por eso mismo, fáciles de usar mal: un break colocado sin criterio convierte un bucle legible en un laberinto, así que aprenderemos también cuándo no usarlas.
Contenido
break: salir del bucle antes de tiempocontinue: saltarse una vuelta- La cláusula
elsede los bucles - Bucles anidados
- Centinelas y banderas para salir de bucles anidados
match/case: clasificar con elegancia- Buenas prácticas del control de flujo
- TareaFácil v0.5.3: tres intentos y clasificación con
match - Errores comunes y consejos
- Ejercicios
- Conclusión
break: salir del bucle antes de tiempo
break: salir del bucle antes de tiempobreak interrumpe el bucle inmediatamente. No termina la vuelta en curso ni evalúa otra vez la condición: abandona el bucle y el programa continúa en la primera instrucción que haya después.
for i in range(5):
respuesta = input(f"Tarea {i + 1} (Intro vacio para terminar): ").strip()
if respuesta == "":
print("Registro interrumpido por el usuario.")
break
print(f"Registrada: {respuesta}")
print("Fin del registro.")Si el usuario pulsa Intro en la tercera tarea, las vueltas cuarta y quinta no se ejecutan. El bucle estaba preparado para cinco iteraciones y ha hecho tres.
flowchart TD
A["Inicio de la vuelta"] --> B{"Condicion de break ?"}
B -->|True| F["Salir del bucle"]
B -->|False| C{"Condicion de continue ?"}
C -->|True| A
C -->|False| D["Resto del cuerpo"]
D --> A
F --> G["Instruccion posterior al bucle"]
El uso más frecuente de break es el bucle infinito controlado: while True: da vueltas para siempre y la única salida es un break dentro del cuerpo. Parece temerario, pero es un patrón perfectamente respetable cuando la condición de salida solo puede evaluarse a mitad de la vuelta, es decir, después de haber leído un dato.
while True:
dias_texto = input("Dias estimados (0 para cancelar): ").strip()
if dias_texto == "0":
break
if dias_texto.isdigit():
print(f"Anotadas {dias_texto} jornadas.")Sobre este esqueleto se construye el bucle principal de una aplicación, que es justo el tema de Menús interactivos.
continue: saltarse una vuelta
continue: saltarse una vueltacontinue no sale del bucle: abandona la vuelta actual y salta directamente a la siguiente. Todo lo que hubiera después de él en el cuerpo se ignora, solo en esa iteración.
total_jornadas = 0
for i in range(4):
dias_texto = input(f"Jornadas de la tarea {i + 1}: ").strip()
if not dias_texto.isdigit():
print(" Dato no numerico: tarea descartada del total.")
continue
total_jornadas += int(dias_texto)
print(f" Acumulado: {total_jornadas} jornadas")
print(f"Total valido: {total_jornadas} jornadas")Con las entradas 4, ocho, 2 y 1, la segunda vuelta imprime el aviso y salta; las otras tres acumulan. El total es 7.
continue sirve para descartar casos al principio del cuerpo y dejar el resto del código sin sangrar. Es el equivalente en bucles de las cláusulas de guarda que viste en 03-01. La alternativa sin continue sería envolver todo el cuerpo en un if, lo cual funciona igual pero añade un nivel de indentación a cada línea.
break |
continue |
|
|---|---|---|
| Qué hace | Termina el bucle entero | Termina solo la vuelta actual |
| Adónde salta | A la instrucción posterior al bucle | A la siguiente iteración |
| Uso típico | Ya tengo lo que buscaba; cancelar | Descartar un elemento y seguir |
- La cláusula
else de los bucles
else de los buclesEsta es una peculiaridad de Python que sorprende incluso a programadores con experiencia en otros lenguajes: tanto for como while admiten un else final. Y no significa lo que parece.
El else de un bucle se ejecuta si el bucle terminó de forma natural, es decir, agotando la secuencia (for) o volviéndose falsa la condición (while); no se ejecuta si el bucle terminó por un break. La forma más fácil de recordarlo es leerlo como un else de break: «si el bucle acabó sin romperse, haz esto». Su caso de uso canónico es la búsqueda que no encontró nada:
EQUIPO = ("Marta", "Luis", "Nuria")
buscado = "Nuria"
for i in range(3):
responsable = input(f"Responsable de la tarea {i + 1}: ").strip().capitalize()
if responsable == buscado:
print(f"Encontrada una tarea de {buscado} en la posicion {i + 1}.")
break
else:
print(f"Ninguna de las 3 tareas es de {buscado}.")Si en alguna vuelta aparece Nuria, el break corta el bucle y el else no se ejecuta. Si se agotan las tres vueltas sin encontrarla, el bucle termina de forma natural y el else avisa de que la búsqueda fue infructuosa.
Sin esta cláusula habría que recurrir a una bandera: inicializar encontrada = False, ponerla a True junto al break, y comprobar if not encontrada: al salir del bucle. Las dos versiones son correctas: la del else es más corta, la de la bandera es más explícita y se entiende sin conocer esta peculiaridad. Muchos equipos prefieren la segunda precisamente por eso; el for...else es una de esas construcciones que hay que saber leer aunque decidas no escribirla.
El while...else funciona igual y es aún menos frecuente: se ejecuta si el while acabó porque su condición se volvió falsa, no si salió por break. Lo usaremos en TareaFácil para el límite de intentos.
- Bucles anidados
Un bucle puede contener otro. El interior se ejecuta completo en cada vuelta del exterior, así que el número total de iteraciones es el producto de ambos.
for tabla in range(2, 4):
for multiplicador in range(1, 4):
print(f"{tabla} x {multiplicador} = {tabla * multiplicador}")
print("---")Se imprimen las tres multiplicaciones de la tabla del 2, un separador, las tres de la tabla del 3 y otro separador: seis vueltas del bucle interior en dos del exterior. Y fíjate en el print("---"): está sangrado al nivel del bucle exterior, así que se ejecuta una vez por tabla, no una vez por multiplicación. En los bucles anidados, la sangría lo es todo.
El caso realista para Estudio Alba es una parrilla de días por persona: la planificación semanal del equipo.
EQUIPO = ("Marta", "Luis", "Nuria")
for dia in range(1, 6):
print(f"--- Dia {dia} ---")
for posicion in range(3):
persona = EQUIPO[posicion]
horas = input(f"Horas de {persona}: ").strip()
print(f" {persona}: {horas} h")Aquí EQUIPO[posicion] accede al elemento que está en esa posición de la tupla, contando desde 0; es la primera vez que lo hacemos y lo veremos a fondo en Listas y arreglos. El bucle exterior da 5 vueltas y el interior 3 en cada una: 15 preguntas en total.
Y ahí está el coste de anidar. Con un bucle, doblar los datos dobla el trabajo; con dos bucles anidados, doblar los datos lo cuadruplica, porque el total es el producto. Cinco días por tres personas son 15 vueltas, pero 100 días por 100 personas son 10.000. Esa relación entre el tamaño del problema y el trabajo que cuesta se llama complejidad, y se estudia en Eficiencia y notación Big-O.
- Centinelas y banderas para salir de bucles anidados
Aquí aparece una limitación importante: break solo rompe el bucle en el que está. Si estás dentro del bucle interior y quieres abandonar los dos, un solo break no basta: sale del interior y el exterior sigue dando vueltas. La solución habitual es una bandera que el bucle exterior consulta:
EQUIPO = ("Marta", "Luis", "Nuria")
cancelado = False
for dia in range(1, 6):
for posicion in range(3):
horas = input(f"Horas de {EQUIPO[posicion]} el dia {dia} (x para salir): ").strip()
if horas.lower() == "x":
cancelado = True
break # sale del bucle interior
if cancelado:
print("Planificacion cancelada.")
break # sale del bucle exteriorEl mecanismo tiene tres piezas: la bandera se inicializa a False antes de todo, se pone a True justo antes del break interior, y el bucle exterior la comprueba nada más terminar el interior. La "x" que teclea el usuario es el centinela: un valor convenido que no es un dato válido y cuya única función es señalar el final.
Un centinela debe cumplir dos condiciones: no puede confundirse con un dato legítimo (si las horas pueden ser 0, el 0 no sirve como centinela) y debe estar anunciado en el mensaje que ve el usuario. Un centinela secreto es un dato perdido.
match / case: clasificar con elegancia
match / case: clasificar con eleganciaDesde Python 3.10 existe match, pensada para el caso concretísimo de comparar un valor contra varias alternativas. Es exactamente lo que hace una cadena de elif con el mismo == repetido, pero se lee mucho mejor.
prioridad = "media"
match prioridad:
case "alta":
mensaje = "Plazo: hoy mismo."
case "media":
mensaje = "Plazo: esta semana."
case "baja":
mensaje = "Plazo: cuando haya hueco."
case _:
mensaje = "Prioridad desconocida."
print(mensaje) # Plazo: esta semana.Las reglas son cuatro. match valor: abre la estructura con el valor que se va a comparar; cada case propone un patrón y se ejecuta solo el primero que coincida; case _: es el caso por defecto, equivalente al else, porque el guion bajo significa «cualquier cosa»; y terminado el case elegido el flujo sale del match, sin necesidad de escribir ningún break como exigen C o Java.
Un case puede recoger varias alternativas con la barra vertical |, que aquí se lee «o»:
match prioridad:
case "alta" | "urgente" | "critica":
mensaje = "Plazo: hoy mismo."
case "media" | "normal":
mensaje = "Plazo: esta semana."
case _:
mensaje = "Plazo: cuando haya hueco."Esa sola línea sustituye a if prioridad == "alta" or prioridad == "urgente" or prioridad == "critica":, y hace evidente que las tres palabras son sinónimos de lo mismo.
if / elif |
match / case |
|
|---|---|---|
| Compara | Cualquier condición booleana | Un valor contra patrones |
Rangos y comparaciones (dias > 15) |
Sí | No de forma natural |
| Varias alternativas por rama | in (...) o varios or |
Patrón con ` |
| Caso por defecto | else |
case _ |
| Versión mínima de Python | Cualquiera | 3.10 |
La regla de elección es simple: si comparas un valor contra una lista de posibilidades concretas, match; si las condiciones son cálculos o rangos (dias > 15, horas <= 40), if / elif. Y si tu código debe funcionar en Python 3.9 o anterior, match no existe.
- Buenas prácticas del control de flujo
Estas herramientas se pagan en legibilidad, así que conviene un código de conducta.
- Nada de
breakgratuito. Si la condición de salida cabe en la cabecera delwhile, escríbela ahí:while intentos < 3:es más honesto quewhile True:con unbreakescondido veinte líneas más abajo. Y un solo punto de salida por bucle siempre que se pueda, porque tresbreakrepartidos obligan a leer el cuerpo entero para saber cuándo termina. - Siempre un límite de intentos. Un bucle que pregunta hasta obtener un dato válido puede preguntar eternamente. Tres intentos y una salida digna es mejor experiencia que un interrogatorio infinito.
- Cuidado con anidar. Dos niveles son manejables; tres, sospechosos. A partir de ahí, replantea el problema.
- TareaFácil v0.5.3: tres intentos y clasificación con
match
matchLa versión 0.5.2 insistía indefinidamente. Ahora aplicamos la cuarta buena práctica: tres intentos y nos rendimos con un valor por defecto anunciado, no inventado en secreto. Y la clasificación de la prioridad pasa a un match.
# tareafacil.py - Estudio Alba
# Version 0.5.3: limite de intentos y clasificacion con match
ANCHO = 46
MAX_INTENTOS = 3
PRIORIDADES = ("alta", "media", "baja")
EQUIPO = ("Marta", "Luis", "Nuria")
titulo = input("Titulo de la tarea : ").strip()
titulo = titulo if titulo != "" else "(sin titulo)"
responsable = input("Responsable (Marta/Luis/Nuria): ").strip().capitalize()
while responsable not in EQUIPO:
responsable = input("No es del equipo. Responsable: ").strip().capitalize()
# --- Prioridad: tres intentos y nos rendimos ---
intentos = 0
prioridad = ""
while intentos < MAX_INTENTOS:
prioridad = input(f"Prioridad (alta/media/baja) [{intentos + 1}/{MAX_INTENTOS}]: ").strip().lower()
if prioridad in PRIORIDADES:
break
print(f" '{prioridad}' no es valida.")
intentos += 1
else:
prioridad = "media"
print(f"Agotados los {MAX_INTENTOS} intentos. Se asigna prioridad 'media'.")
# --- Clasificacion del plazo con match ---
match prioridad:
case "alta":
plazo = "Entrega hoy mismo"
case "media":
plazo = "Entrega esta semana"
case "baja":
plazo = "Entrega cuando haya hueco"
case _:
plazo = "Plazo por determinar"
print("=" * ANCHO)
print(f"{'Titulo':<16}{titulo:>{ANCHO - 16}}")
print(f"{'Responsable':<16}{f'{responsable} ({prioridad})':>{ANCHO - 16}}")
print(f"{'Plazo':<16}{plazo:>{ANCHO - 16}}")
print("=" * ANCHO)Aquí está la pieza clave del apartado 3, ahora con sentido pleno: el while tiene un else que solo se ejecuta si se agotaron los tres intentos. Si Marta acierta al segundo, el break corta el bucle y el else no llega a ejecutarse, de modo que la prioridad conserva el valor que ella escribió. Si falla tres veces, la condición intentos < MAX_INTENTOS se vuelve falsa por sí sola, el bucle termina de forma natural y el else aplica el valor por defecto avisando de ello.
Prioridad (alta/media/baja) [1/3]: urgentisima 'urgentisima' no es valida. Prioridad (alta/media/baja) [2/3]: muy alta 'muy alta' no es valida. Prioridad (alta/media/baja) [3/3]: altisima 'altisima' no es valida. Agotados los 3 intentos. Se asigna prioridad 'media'. ============================================== Responsable Nuria (media) Plazo Entrega esta semana ==============================================
Compara las tres versiones del mismo problema y verás la evolución del criterio: la 0.5.1 corregía el dato en silencio, la 0.5.2 exigía sin límite y la 0.5.3 negocia: insiste un número razonable de veces y, si no hay manera, sigue adelante diciendo exactamente qué ha decidido y por qué.
Errores Comunes y Consejos
Creer que el else de un bucle se ejecuta cuando el bucle no da vueltas. No: se ejecuta cuando el bucle no se rompe con break. Un for de cero vueltas ejecuta su else, porque terminó de forma natural.
Esperar que un break salga de dos bucles. Solo rompe el bucle que lo contiene directamente. Para salir de un anidamiento hacen falta una bandera y un break por nivel.
Colocar continue antes de la actualización en un while. Es el bucle infinito más traicionero: continue salta a la condición sin haber incrementado el contador. En un for no ocurre, porque de la variable de control se encarga Python.
Colocar mal los case. Gana el primero que coincide, así que un case _: situado arriba deja inalcanzables a todos los demás. Y case dias > 15: no es un patrón válido: los rangos son territorio de if / elif.
Confundir el nivel de sangría en bucles anidados. Una instrucción sangrada un nivel de más se ejecuta N veces en lugar de una. Es el error que más resultados extraños produce, y no da ningún aviso.
Consejo: nombra las banderas como preguntas. cancelado, encontrada, hay_urgente. Así if cancelado: se lee como una frase y el código se explica solo.
Consejo: pon un contador de seguridad en los while True, aunque creas que la salida está garantizada. Un while True sin break alcanzable es un programa colgado, y en producción eso es un incidente.
Ejercicios
Ejercicio 1: Predecir la salida
Sin ejecutar nada, indica qué imprime cada fragmento.
# a)
for i in range(5):
if i == 3:
break
print(i, end=" ")
else:
print("completo")
# b)
for i in range(5):
if i % 2 == 0:
continue
print(i, end=" ")
else:
print("completo")
# c)
prioridad = "urgente"
match prioridad:
case "alta" | "urgente":
print("hoy")
case _:
print("otro dia")Ejercicio 2: Buscador de tareas de Nuria
Escribe buscar_nuria.py que pregunte cuántas tareas hay en el lote y luego pida, una a una, el responsable de cada tarea. En cuanto encuentre la primera de Nuria debe imprimir en qué posición está y dejar de preguntar. Si recorre todas sin encontrarla, debe avisar de que Nuria no tiene tareas asignadas, usando la cláusula else del for. Descarta con continue las entradas vacías, sin que cuenten como respuesta válida.
Ejercicio 3: Parrilla semanal con cancelación
Escribe parrilla_semana.py que recorra con bucles anidados los 5 días laborables y las 3 personas de EQUIPO, pidiendo las horas previstas de cada persona cada día. Debe acumular el total de horas de la semana, permitir cancelar en cualquier momento tecleando x (saliendo de los dos bucles con una bandera) y, al terminar, clasificar la carga total con match sobre un valor calculado: "ligera" por debajo de 80 horas, "normal" hasta 110 y "excesiva" por encima. Pista: calcula primero la categoría con if/elif y usa match para el mensaje.
Soluciones
Solución 1.
| Salida | Por qué | |
|---|---|---|
| a | 0 1 2 |
El break en i == 3 corta el bucle, así que el else no se ejecuta |
| b | 1 3 completo |
continue salta los pares; el bucle acaba sin break, luego el else sí se ejecuta |
| c | hoy |
El patrón "alta" | "urgente" acepta las dos palabras |
El contraste entre a y b es la lección entera del else de bucle: lo que decide su ejecución no es cuántas vueltas se dieron, sino si hubo break.
Solución 2.
# buscar_nuria.py - Estudio Alba
BUSCADO = "Nuria"
numero_tareas = int(input("Cuantas tareas hay en el lote? "))
for i in range(numero_tareas):
responsable = input(f"Responsable de la tarea {i + 1}: ").strip().capitalize()
if responsable == "":
print(" Entrada vacia, se ignora.")
continue
if responsable == BUSCADO:
print(f"Primera tarea de {BUSCADO}: la numero {i + 1}.")
break
else:
print(f"{BUSCADO} no tiene ninguna tarea asignada en este lote.")Cuidado con un matiz del enunciado: el continue ignora la entrada vacía, pero la vuelta se consume igual, porque el for avanza por su secuencia pase lo que pase. Si quisieras que una entrada vacía no gastara turno, necesitarías un while con tu propio contador, que solo incrementarías tras una respuesta válida.
Solución 3.
# parrilla_semana.py - Estudio Alba
EQUIPO = ("Marta", "Luis", "Nuria")
total_horas = 0
cancelado = False
for dia in range(1, 6):
for posicion in range(3):
persona = EQUIPO[posicion]
entrada = input(f"Dia {dia} - horas de {persona} (x para salir): ").strip().lower()
if entrada == "x":
cancelado = True
break
if entrada.isdigit():
total_horas += int(entrada)
if cancelado:
break
if total_horas < 80:
categoria = "ligera"
elif total_horas <= 110:
categoria = "normal"
else:
categoria = "excesiva"
match categoria:
case "ligera":
print(f"{total_horas} h: semana ligera, cabe mas trabajo.")
case "normal":
print(f"{total_horas} h: semana normal, planificacion sana.")
case "excesiva":
print(f"{total_horas} h: semana excesiva, hay que repartir.")Fíjate en el reparto de papeles, que es el mensaje de fondo de esta lección: el if / elif se ocupa de los rangos numéricos, porque match no sabe compararlos; y match se ocupa de traducir la categoría ya calculada en un mensaje, que es exactamente comparar un valor contra tres posibilidades. Cada herramienta en lo suyo.
Conclusión
Ya controlas el flujo con precisión. Sabes cortar un bucle con break —incluido el patrón while True con salida controlada— y descartar una vuelta con continue, que es la cláusula de guarda de los bucles. Entiendes la cláusula else de for y while, que no significa «si no hubo vueltas» sino «si no hubo break», y conoces su caso canónico: la búsqueda que termina sin encontrar nada.
Sabes anidar bucles para recorrer parrillas de dos dimensiones —días por persona—, tienes claro que el trabajo total es el producto de ambos (con el aviso de coste que retomará la lección de eficiencia), y sabes salir de un anidamiento con una bandera y un break por nivel, señalizando el final con un centinela anunciado al usuario. Y manejas match / case con sus patrones | y su case _, sabiendo que sirve para comparar un valor contra alternativas concretas, nunca para rangos.
TareaFácil está en la versión 0.5.3 y ha aprendido a negociar: da tres intentos, avisa en cada fallo, y si se agotan aplica un valor por defecto diciéndolo en voz alta. La cláusula else del while distingue con exactitud «lo acertó» de «se rindió», y un match traduce la prioridad en un plazo de entrega.
Y sin embargo, el programa sigue siendo un guion de un solo acto: se ejecuta de arriba abajo, registra una tarea y muere. Si Marta quiere consultar la ficha que acaba de crear, o cambiar la prioridad que asignó mal, no le queda otra que volver a lanzarlo y teclearlo todo de nuevo. Le falta lo que convierte un script en una aplicación: un menú que se muestra, espera una orden, la ejecuta y vuelve a mostrarse. Tienes ya todas las piezas —while True, break, validación, if/elif y match— y en Menús interactivos y bucles de aplicación las montaremos juntas para cerrar el módulo.
Fundamentos de la Programación
Módulo 1: Introducción a la Programación
- ¿Qué es la programación?
- Historia de la programación
- Lenguajes de programación
- Entornos de desarrollo
- Del problema al algoritmo
Módulo 2: Conceptos Básicos
- Variables y tipos de datos
- Operadores y expresiones
- Entrada y salida de datos
- Conversión de tipos y validación de datos
Módulo 3: Estructuras de Control
Módulo 4: Funciones y Procedimientos
- Definición y uso de funciones
- Parámetros y retorno de valores
- Ámbito de variables
- Descomponer un programa en funciones
- Funciones como valores: lambda y orden superior
Módulo 5: Estructuras de Datos
- Listas y arreglos
- Cadenas de caracteres
- Diccionarios y conjuntos
- Tuplas y estructuras anidadas
- Guardar datos en archivos: texto, CSV y JSON
Módulo 6: Algoritmos Básicos
Módulo 7: Objetos y Organización del Código
- De los datos a los objetos: clases e instancias
- Atributos, métodos y constructor
- Colecciones de objetos
- Módulos, paquetes e importaciones
Módulo 8: Buenas Prácticas y Herramientas
- Documentación y comentarios
- Depuración y manejo de errores
- Control de versiones
- Pruebas automatizadas
- Estilo, legibilidad y refactorización
