La lección anterior acabó con un programa que ya sabe decidir, pero que sigue siendo incapaz de hacer lo más natural del mundo: insistir. Cuando Marta escribe urgentisima donde iba una prioridad, TareaFácil interpreta lo que puede y, si no lo entiende, inventa un valor por defecto. Lo razonable sería volver a preguntar. Y para volver a preguntar hay que repetir.
Repetir es la segunda gran estructura de control, y la que convierte un programa en algo que trabaja de verdad: recorrer datos, insistir hasta obtener una respuesta válida, acumular totales, calcular medias. Sin bucles, un programa solo puede hacer tantas cosas como líneas tenga escritas; con bucles, diez líneas pueden procesar diez datos o diez mil.
En esta lección aprenderás las dos formas de repetir en Python —while y for—, cuándo usar cada una, los cuatro patrones que aparecen una y otra vez en el código real, y cómo cerrar por fin el cabo suelto que arrastramos desde el módulo 2.
Contenido
- Repetir sin repetir código
- El bucle
while - Bucles infinitos y cómo salir de ellos
- El bucle
fory la funciónrange() - Recorrer una cadena carácter a carácter
whilefrente afor: cuándo usar cada uno- Los cuatro patrones clásicos
- TareaFácil v0.5.2: insistir hasta que el dato sea válido
- Errores comunes y consejos
- Ejercicios
- Conclusión
- Repetir sin repetir código
Imagina que Marta quiere ver la cuenta atrás de los tres días que faltan para entregar el cartel de la feria del libro. Con lo que sabías hasta ahora, la única salida era escribir tres print casi idénticos. Funciona para tres días; para treinta, son treinta líneas, y para un número que el usuario decide mientras el programa se ejecuta, es sencillamente imposible.
Un bucle (o iteración) resuelve esto: escribes el bloque una sola vez y le dices al programa cuántas veces, o hasta cuándo, debe ejecutarlo. Cada pasada del bloque se llama iteración.
Python ofrece dos bucles, y la diferencia entre ellos es solo esta: while repite mientras una condición sea verdadera, sin saber de antemano cuántas vueltas dará, y for repite para cada elemento de una secuencia, con el número de vueltas determinado antes de empezar.
- El bucle
while
whileSu sintaxis es idéntica a la del if: palabra clave, condición, dos puntos y bloque indentado de 4 espacios. La diferencia es que, al terminar el bloque, el flujo vuelve a la condición en vez de seguir adelante.
dias_restantes = 3 # 1. inicializacion
while dias_restantes > 0: # 2. condicion de continuacion
print(f"Quedan {dias_restantes} dias") # 3. cuerpo
dias_restantes -= 1 # 4. actualizacion
print("Plazo agotado.")Se imprime Quedan 3 dias, Quedan 2 dias, Quedan 1 dias y, ya fuera del bucle, Plazo agotado.. Todo bucle while correcto tiene esas cuatro piezas, y conviene aprendérselas como una lista de comprobación:
| Pieza | Qué hace | Si falta |
|---|---|---|
| Inicialización | Da un valor inicial a la variable de control | NameError: la variable no existe |
| Condición de continuación | Se evalúa antes de cada vuelta | SyntaxError |
| Cuerpo | El trabajo que se repite | IndentationError |
| Actualización | Modifica la variable para acercarse a la salida | Bucle infinito |
Fíjate en un detalle importante: la condición se evalúa antes de la primera vuelta. Si es falsa desde el principio —por ejemplo, con dias_restantes = 0—, el cuerpo no se ejecuta ni una sola vez. Un while puede dar cero vueltas, y eso casi nunca es un error: suele ser exactamente lo que queremos.
flowchart TD
A["Inicializar variable"] --> B{"Condicion cierta ?"}
B -->|False| E["Continuar el programa"]
B -->|True| C["Ejecutar el cuerpo"]
C --> D["Actualizar variable"]
D --> B
La flecha que vuelve de la actualización a la condición es lo que distingue un bucle de un if. Es literalmente un ciclo.
- Bucles infinitos y cómo salir de ellos
Si olvidas la actualización, la condición nunca cambia y el bucle no termina jamás:
dias_restantes = 3
while dias_restantes > 0:
print(f"Quedan {dias_restantes} dias") # falta dias_restantes -= 1El programa imprime Quedan 3 dias para siempre, hasta llenar la pantalla. No es un error de sintaxis: Python no puede saber que te has equivocado, porque un bucle sin fin es perfectamente legal (de hecho, en la lección Menús interactivos lo usaremos a propósito).
Cómo detenerlo: pulsa Ctrl+C en la terminal. Eso interrumpe el programa y muestra un KeyboardInterrupt. No es un fallo de tu código: es la señal de que tú has cortado la ejecución.
Los tres descuidos que los producen son casi siempre los mismos: no actualizar la variable de control dentro del cuerpo, actualizarla en la dirección equivocada (dias_restantes += 1 cuando la condición es > 0), o actualizarla fuera del bucle, es decir, sin la sangría correcta, con lo que solo se ejecuta al terminar. Antes de ejecutar un while, hazte esta pregunta: ¿qué instrucción de este cuerpo hará que la condición acabe siendo falsa? Si no sabes señalarla con el dedo, tienes un bucle infinito.
- El bucle
for y la función range()
for y la función range()El for repite un bloque para cada elemento de una secuencia. Cuando lo que quieres es simplemente repetir n veces, la secuencia la genera range(), que produce una sucesión de números enteros.
La variable numero es la variable de control: Python la crea, le asigna el primer valor de la secuencia, ejecuta el cuerpo, le asigna el siguiente, y así hasta agotarla. No hace falta inicializarla ni actualizarla: de eso se encarga el for, y por eso un for nunca produce un bucle infinito por descuido.
range() admite tres formas, y conviene dominar las tres:
| Forma | Genera | Ejemplo | Valores |
|---|---|---|---|
range(n) |
De 0 a n−1 | range(4) |
0, 1, 2, 3 |
range(a, b) |
De a hasta b−1 | range(1, 5) |
1, 2, 3, 4 |
range(a, b, paso) |
De a a b−1 saltando de paso en paso |
range(0, 10, 3) |
0, 3, 6, 9 |
range(a, b, -paso) |
Cuenta atrás | range(3, 0, -1) |
3, 2, 1 |
La regla que hay que grabarse: el límite superior nunca se incluye. range(5) da cinco números, pero el 5 no está entre ellos; es la fuente número uno de errores «de uno» (off-by-one) en principiantes.
for dia in range(1, 6): # los 5 dias laborables
print(f"Dia {dia} de la planificacion")
for dias_restantes in range(3, 0, -1): # cuenta atras: 3, 2, 1
print(f"Quedan {dias_restantes} dias")flowchart TD
A["range genera la secuencia"] --> B{"Quedan elementos ?"}
B -->|False| E["Continuar el programa"]
B -->|True| C["Asignar el siguiente<br/>a la variable de control"]
C --> D["Ejecutar el cuerpo"]
D --> B
Compáralo con el diagrama del while: la estructura es la misma, pero aquí no hay actualización manual porque la propia secuencia decide cuándo se acaba.
- Recorrer una cadena carácter a carácter
range() no es la única secuencia que un for puede recorrer. Una cadena de texto también lo es: sus elementos son sus caracteres, de izquierda a derecha.
Esto abre la puerta a analizar texto sin conocer aún estructuras de datos. Por ejemplo, contar cuántos espacios tiene un título, que es una forma rudimentaria de contar palabras:
titulo = "Cartel de la feria del libro"
espacios = 0
for caracter in titulo:
if caracter == " ":
espacios += 1
print(f"El titulo tiene {espacios + 1} palabras") # 6 palabrasFíjate en la combinación: un for que recorre, un if que decide y un contador que acumula. Esa tríada es el 80% del código que escribirás. Recorrer colecciones de tareas con for —una lista con todas las tareas del estudio— es exactamente la misma idea, y llegará en Listas y arreglos.
while frente a for: cuándo usar cada uno
while frente a for: cuándo usar cada unoLos dos bucles pueden simular al otro, pero cada problema tiene su forma natural.
while |
for |
|
|---|---|---|
| Se usa cuando | No sabes cuántas vueltas darás | Conoces el número de vueltas o la secuencia |
| Lo gobierna | Una condición | Una secuencia |
| Variable de control | La inicializas y actualizas tú | La gestiona Python |
| Riesgo de bucle infinito | Sí, si olvidas actualizar | Prácticamente ninguno |
| Ejemplo típico | Pedir un dato hasta que sea válido | Repetir un cálculo 12 veces |
La pregunta que lo resuelve casi siempre: ¿puedo saber el número de repeticiones antes de empezar? Si la respuesta es sí, for. Si depende de algo que ocurrirá dentro del bucle —lo que teclee el usuario, un cálculo que converge—, while.
- Los cuatro patrones clásicos
Casi todo lo que se hace con bucles cae en uno de estos cuatro moldes. Los cuatro comparten la misma anatomía: una variable que se inicializa antes del bucle y se modifica dentro.
Contador: cuenta cuántas veces ocurre algo.
tareas_altas = 0
for i in range(4):
prioridad = input("Prioridad de la tarea: ").strip().lower()
if prioridad == "alta":
tareas_altas += 1
print(f"Hay {tareas_altas} tareas de prioridad alta")Acumulador: suma los valores que va viendo.
total_dias = 0
for i in range(3):
total_dias += int(input("Dias estimados: "))
print(f"El lote suma {total_dias} jornadas")Con el acumulador y el contador juntos sale la media, que es una división ordinaria: total_dias / numero_tareas. Recuerda de la lección 02-02 que / siempre devuelve un float.
Máximo y mínimo: busca el valor más grande o más pequeño.
mayor = 0 # menor que cualquier estimacion valida
for i in range(3):
dias = int(input("Dias estimados: "))
if dias > mayor:
mayor = dias
print(f"La tarea mas larga son {mayor} jornadas")La clave está en la inicialización: mayor = 0 funciona porque ninguna estimación válida es menor o igual que cero. Para el mínimo el truco simétrico no vale (menor = 0 nunca sería superado), así que se inicializa con un valor deliberadamente alto, como menor = 9999, o con el primer dato leído.
Bandera booleana: recuerda si algo ha ocurrido al menos una vez.
hay_urgente = False
for i in range(3):
prioridad = input("Prioridad de la tarea: ").strip().lower()
if prioridad == "alta":
hay_urgente = True
if hay_urgente:
print("AVISO: el lote contiene urgencias.")La bandera se inicializa a False y solo puede pasar a True; nunca vuelve atrás. Es el patrón que responde a preguntas del tipo «¿hay alguna…?».
- TareaFácil v0.5.2: insistir hasta que el dato sea válido
Ahora sí. El bucle de validación es el patrón que llevamos prometiendo desde la lección 02-04, y su forma es siempre la misma: leer el dato antes del bucle, y repetir mientras no sea válido.
prioridad = input("Prioridad (alta/media/baja): ").strip().lower()
while prioridad not in PRIORIDADES:
print(f"'{prioridad}' no es una prioridad valida.")
prioridad = input("Prioridad (alta/media/baja): ").strip().lower()Léelo despacio, porque tiene una trampa clásica: el input() aparece dos veces, una antes del bucle y otra dentro. La primera lectura es necesaria para que la condición tenga algo que evaluar; la segunda es la actualización de la que hablábamos en el apartado 2. Si olvidas la de dentro, el valor nunca cambia y tienes un bucle infinito que te repite el mismo reproche para siempre. Con esta pieza, TareaFácil ya no inventa nada: pregunta hasta que le den un dato correcto. Esta es la versión 0.5.2 completa, donde el mensaje de error va incorporado al propio prompt de la segunda lectura para no alargar el código.
# tareafacil.py - Estudio Alba
# Version 0.5.2: entrada insistente y resumen del lote
ANCHO = 46
HORAS_POR_JORNADA = 8
PRIORIDADES = ("alta", "media", "baja")
EQUIPO = ("Marta", "Luis", "Nuria")
DIAS_MIN = 1
DIAS_MAX = 365
# --- 1. Titulo: insistir mientras este vacio ---
titulo = input("Titulo de la tarea : ").strip()
while titulo == "":
titulo = input("Vacio. Titulo de la tarea : ").strip()
# --- 2. Responsable: insistir mientras no sea del equipo ---
responsable = input("Responsable (Marta/Luis/Nuria): ").strip().capitalize()
while responsable not in EQUIPO:
responsable = input("No es del equipo. Responsable: ").strip().capitalize()
# --- 3. Prioridad: insistir mientras no este en el catalogo ---
prioridad = input("Prioridad (alta/media/baja) : ").strip().lower()
while prioridad not in PRIORIDADES:
prioridad = input("No valida. Prioridad : ").strip().lower()
# --- 4. Dias: insistir mientras no sea un numero dentro de rango ---
dias_texto = input("Dias estimados : ").strip()
while not dias_texto.isdigit() or not DIAS_MIN <= int(dias_texto) <= DIAS_MAX:
dias_texto = input(f"Entero de {DIAS_MIN} a {DIAS_MAX}. Dias : ").strip()
dias_estimados = int(dias_texto)
# --- Ficha de la tarea ---
horas_estimadas = dias_estimados * HORAS_POR_JORNADA
print("=" * ANCHO)
print(f"{'Titulo':<16}{titulo:>{ANCHO - 16}}")
print(f"{'Responsable':<16}{f'{responsable} ({prioridad})':>{ANCHO - 16}}")
print(f"{'Dias / horas':<16}{f'{dias_estimados} / {horas_estimadas}':>{ANCHO - 16}}")
print("=" * ANCHO)Observa la condición del cuarto bucle, que reúne todo lo del módulo: not dias_texto.isdigit() or not DIAS_MIN <= int(dias_texto) <= DIAS_MAX. Gracias al cortocircuito del or, si el texto no es un número la segunda parte no se evalúa y el int() nunca llega a ejecutarse con basura. Una sola condición valida tipo y rango a la vez.
Ninguna de las cuatro entradas puede ya provocar un valor inventado. Compara con la versión 0.5.1: allí el programa corregía; aquí exige. Un fragmento de sesión:
Prioridad (alta/media/baja) : urgentisima No valida. Prioridad : ALTA Dias estimados : cuatro Entero de 1 a 365. Dias : 4
El resumen del lote
Marta suele planificar varias tareas de golpe: sabe cuántas son y quiere saber cuántas jornadas suman y cuál es la media. Como conocemos el número de vueltas antes de empezar, este es territorio del for, con tres de los patrones del apartado 7 a la vez.
# --- Resumen de planificacion del lote ---
numero_tareas = int(input("Cuantas tareas vas a planificar? "))
total_jornadas = 0
mayor = 0
for i in range(numero_tareas):
dias = int(input(f"Jornadas de la tarea {i + 1}: "))
total_jornadas += dias
if dias > mayor:
mayor = dias
media = total_jornadas / numero_tareas
print(f"Total del lote : {total_jornadas} jornadas")
print(f"Media por tarea : {media:.1f} jornadas")
print(f"La mas larga : {mayor} jornadas")Con las jornadas 4, 10 y 1, el resumen sale Total del lote: 15 jornadas, Media por tarea: 5.0 jornadas y La mas larga: 10 jornadas.
Tres detalles que merecen atención. El i + 1 del mensaje existe porque range() empieza en 0 y las personas contamos desde 1. El :.1f es el especificador de f-string de la lección 02-03, necesario porque / devuelve un float con demasiados decimales. Y lo más importante: los datos de cada tarea se pierden en cuanto se lee la siguiente. Podemos sumarlos y compararlos al vuelo, pero no guardarlos: TareaFácil sigue teniendo sitio para una sola tarea. Para almacenar varias hacen falta listas.
Errores Comunes y Consejos
Olvidar la actualización en un while. El error clásico y el que produce bucles infinitos. Antes de ejecutar, señala con el dedo la línea que hará falsa la condición.
Olvidar el segundo input() en un bucle de validación. Si solo lees antes del while, el valor nunca cambia y el programa repite el mismo aviso eternamente. Ctrl+C y a revisar la sangría.
Contar mal con range(). range(5) da 0, 1, 2, 3, 4: el límite superior no se incluye. Si quieres del 1 al 5, escribe range(1, 6).
Inicializar el acumulador dentro del bucle. Poner total = 0 dentro del cuerpo lo reinicia en cada vuelta, y al final vale lo mismo que el último dato: la inicialización va siempre antes del bucle. Y ojo con la variable de control de un for: tras for i in range(3):, i vale 2, no 3, y modificarla dentro del cuerpo no afecta a las vueltas siguientes.
Consejo: escribe primero la condición de salida. Antes de teclear el cuerpo, pregúntate cuándo debe terminar el bucle. Un bucle sin una idea clara de su final es un bucle infinito en potencia.
Consejo: prueba los bucles con cero vueltas. ¿Qué pasa si el usuario dice que va a planificar 0 tareas? El for no da ninguna vuelta y la media provoca una división por cero. Los casos límite (0 vueltas, 1 vuelta) descubren la mayoría de los fallos.
Ejercicios
Ejercicio 1: Predecir la salida
Sin ejecutar nada, indica qué imprime cada fragmento y cuántas vueltas da.
# a)
for i in range(2, 8, 2):
print(i, end=" ")
# b)
n = 5
while n > 5:
n -= 1
print("fin", n)
# c)
total = 0
for i in range(1, 5):
total += i
print(total)Ejercicio 2: Contador de vocales del título
Escribe contar_vocales.py que pida un título de tarea y, recorriéndolo carácter a carácter con un for, cuente cuántas vocales tiene (normaliza a minúsculas antes con .lower() y usa in "aeiou"). Muestra el total y el porcentaje de vocales sobre la longitud del título, con un decimal. Pista: len(titulo) devuelve el número de caracteres.
Ejercicio 3: Reparto de jornadas del equipo
Escribe reparto_equipo.py que pregunte a Marta cuántas tareas quiere repartir y, para cada una, pida las jornadas estimadas (validando con un while que sea un entero entre 1 y 30) y el responsable (validando con otro while que esté en EQUIPO). Al terminar debe mostrar el total de jornadas del lote, la media por tarea y si hay alguna tarea de más de 15 jornadas, usando una bandera booleana.
Soluciones
Solución 1.
| Salida | Vueltas | |
|---|---|---|
| a | 2 4 6 |
3 — range(2, 8, 2) genera 2, 4 y 6; el 8 queda fuera |
| b | fin 5 |
0 — la condición 5 > 5 es falsa desde el principio, así que el cuerpo no se ejecuta nunca |
| c | 10 |
4 — acumula 1+2+3+4; el 5 no entra en range(1, 5) |
El caso b es el que más sorprende: un while puede dar cero vueltas. Y el c recuerda que el límite superior de range() siempre queda excluido.
Solución 2.
# contar_vocales.py - Estudio Alba
titulo = input("Titulo de la tarea: ").strip()
while titulo == "":
titulo = input("El titulo no puede estar vacio. Titulo: ").strip()
vocales = 0
for caracter in titulo.lower():
if caracter in "aeiou":
vocales += 1
porcentaje = vocales / len(titulo) * 100
print(f"'{titulo}' tiene {len(titulo)} caracteres y {vocales} vocales ({porcentaje:.1f}%)")Dos apuntes. El .lower() se aplica sobre la cadena que recorre el for, no sobre cada carácter: normalizar una vez es más barato y más claro que hacerlo en cada vuelta. Y caracter in "aeiou" usa el operador in de la lección 02-02, que sobre cadenas comprueba si un texto está contenido en otro; con un solo carácter equivale a preguntar si es una de esas cinco letras.
Solución 3.
# reparto_equipo.py - Estudio Alba
EQUIPO = ("Marta", "Luis", "Nuria")
numero_tareas = int(input("Cuantas tareas vas a repartir? "))
total_jornadas = 0
hay_tarea_larga = False
for i in range(numero_tareas):
print(f"--- Tarea {i + 1} ---")
dias_texto = input("Jornadas estimadas (1-30): ").strip()
while not dias_texto.isdigit() or not 1 <= int(dias_texto) <= 30:
dias_texto = input("Debe ser un entero de 1 a 30. Jornadas: ").strip()
responsable = input("Responsable (Marta/Luis/Nuria): ").strip().capitalize()
while responsable not in EQUIPO:
responsable = input("No es del equipo. Responsable: ").strip().capitalize()
total_jornadas += int(dias_texto)
if int(dias_texto) > 15:
hay_tarea_larga = True
media = total_jornadas / numero_tareas
print(f"Total: {total_jornadas} jornadas | Media: {media:.1f} por tarea")
if hay_tarea_larga:
print("AVISO: hay tareas de mas de 15 jornadas. Conviene partirlas.")Fíjate en la estructura: un for que sabe cuántas vueltas dará contiene dos while que no lo saben. Esa combinación —for para lo contable, while para lo insistente— es la que aparece en el código real. Y la bandera hay_tarea_larga se declara antes del for, porque debe sobrevivir a todas las vueltas; si estuviera dentro, se reiniciaría en cada tarea y al final solo reflejaría la última.
Conclusión
Ya sabes repetir. Conoces el bucle while con sus cuatro piezas —inicialización, condición de continuación, cuerpo y actualización—, sabes que la condición se evalúa antes de cada vuelta (y que por eso un while puede dar cero vueltas), y sabes qué es un bucle infinito, por qué ocurre y que se sale con Ctrl+C. Conoces el bucle for con las tres formas de range() —recordando que el límite superior nunca se incluye— y su capacidad de recorrer una cadena carácter a carácter. Y tienes el criterio para elegir: si puedes contar las vueltas antes de empezar, for; si dependen de lo que ocurra dentro, while.
Tienes además los cuatro patrones que resuelven la mayoría de los problemas con bucles: contador, acumulador, máximo/mínimo y bandera booleana, todos con la misma anatomía de variable inicializada fuera y modificada dentro. Y TareaFácil, en su versión 0.5.2, ha cerrado por fin el cabo suelto que arrastrábamos desde el módulo 2: ya no corrige ni inventa datos, insiste hasta que Marta escribe algo válido, con un bucle de validación por cada campo; además sabe resumir un lote de tareas —total, media y máximo— aunque no pueda guardarlas.
Quedan cabos, y son los que abren la próxima lección. Si Marta se equivoca cinco veces seguidas, el programa la interroga cinco veces sin piedad: no hay forma de rendirse tras un número razonable de intentos, ni de saltarse una vuelta concreta, ni de cortar un bucle en mitad del cuerpo cuando ya se sabe la respuesta. Tampoco sabemos aún combinar dos bucles, uno dentro de otro, para recorrer una parrilla de días por persona. Todo eso es control fino del flujo, y es lo que aprenderás en Estructuras de control avanzadas: break, continue, la sorprendente cláusula else de los bucles, los bucles anidados y la sentencia match para clasificar valores con más elegancia que una cadena de elif.
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
