En la lección anterior, Papyrus aprendió a decidir: si hay stock, vende; si el cliente es socio, descuenta. Pero cerramos con un problema pendiente: Ana tiene un catálogo entero, y escribir un if por cada libro no escala. La solución son los bucles, estructuras que repiten un bloque de código tantas veces como haga falta. En esta lección dominarás los dos bucles de Python: for, para recorrer colecciones y secuencias de números, y while, para repetir mientras se cumpla una condición. Con ellos recorreremos el catálogo completo de Papyrus y sumaremos la caja del día en unas pocas líneas.
Contenido
- El bucle
fory la funciónrange() range()con inicio, fin y paso- Recorrer cadenas y listas sencillas con
for - El bucle
while: repetir mientras se cumpla una condición - Contadores y acumuladores: la caja del día
- Bucles anidados
enumerate(): índice y valor a la vez- ¿
forowhile? Cuándo usar cada uno
El bucle for y la función range()
Un bucle for repite un bloque de código una vez por cada elemento de una secuencia. Su forma más básica usa range(), una función que genera una secuencia de números enteros:
Salida:
Desglosemos la sintaxis pieza a pieza:
foryinson palabras reservadas: se leen "para cadanumeroenrange(5)".numeroes la variable de bucle: en cada vuelta (cada iteración) toma el siguiente valor de la secuencia. Puedes llamarla como quieras, siguiendo snake_case.range(5)genera los números0, 1, 2, 3, 4. Sí: empieza en 0 y termina justo antes de 5. Genera exactamente 5 números, pero el 5 no está incluido. Esta convención (inicio incluido, fin excluido) es universal en Python y conviene interiorizarla cuanto antes.- Los dos puntos y el bloque indentado funcionan igual que en el
if: todo lo indentado se repite; lo que va después, sin indentar, se ejecuta una sola vez al terminar el bucle.
Un uso inmediato en Papyrus: imprimir la línea separadora del ticket sin copiar y pegar.
NOMBRE_TIENDA = "Papyrus"
print(f"=== {NOMBRE_TIENDA} ===")
for _ in range(3):
print("- Gracias por su compra -")Cuando el valor de la variable de bucle no se usa (solo queremos repetir 3 veces), la convención en Python es llamarla _ (guion bajo). Es una señal para quien lee: "este valor no importa".
range() con inicio, fin y paso
range() acepta hasta tres argumentos, igual que el troceado de secuencias que verás más adelante:
| Llamada | Genera | Significado |
|---|---|---|
range(5) |
0, 1, 2, 3, 4 | Solo stop: desde 0 hasta stop−1 |
range(1, 6) |
1, 2, 3, 4, 5 | start y stop: desde start hasta stop−1 |
range(0, 10, 2) |
0, 2, 4, 6, 8 | start, stop y step: saltando de step en step |
range(10, 0, -1) |
10, 9, ..., 1 | Paso negativo: cuenta hacia atrás |
Ejemplo con las tres formas, numerando cosas de la librería:
# Numerar las estanterías de Papyrus, de la 1 a la 6
for estanteria in range(1, 7):
print(f"Revisando estantería {estanteria}")
# Los días pares del mes, para los pedidos al distribuidor
for dia in range(2, 31, 2):
print(f"Pedido programado el día {dia}")
# Cuenta atrás para la apertura
for segundos in range(3, 0, -1):
print(f"Apertura en {segundos}...")
print("¡Papyrus abre sus puertas!")Detalles a vigilar:
range(1, 7)llega hasta el 6, no hasta el 7 (fin excluido, siempre).- Con paso negativo, start debe ser mayor que stop, o el rango estará vacío y el bucle no ejecutará ninguna vuelta (sin error: simplemente no pasa nada).
range(0)orange(5, 5)también son rangos vacíos válidos.
Recorrer cadenas y listas sencillas con for
La verdadera potencia del for en Python es que no solo recorre números: recorre cualquier secuencia de elementos (lo que llamamos un iterable). Dos casos que ya podemos usar:
Recorrer una cadena
Una cadena es una secuencia de caracteres, así que se puede recorrer letra a letra:
Salida:
En cada iteración, letra vale un carácter de la cadena, en orden. (Recuerda el parámetro end de print() del módulo 1: aquí sustituye el salto de línea por un espacio.)
Recorrer una lista sencilla
Para recorrer el catálogo de Papyrus necesitamos agrupar los títulos en una sola variable. Para eso Python tiene las listas: una colección ordenada de valores, escrita entre corchetes y separada por comas. Las estudiaremos a fondo en el módulo 4; de momento nos basta con saber crearlas y recorrerlas:
Salida:
Lee ese bucle en voz alta: "para cada titulo en catalogo, imprímelo". El for de Python está diseñado para sonar a lenguaje natural. En cada vuelta, titulo apunta al siguiente elemento de la lista, del primero al último.
Y por supuesto, dentro del bucle podemos usar todo lo aprendido en la lección anterior. Combinemos dos listas paralelas (misma posición = mismo libro) con un condicional:
catalogo = ["La Odisea", "Hamlet", "El Quijote"]
stocks = [4, 6, 8]
for i in range(3):
titulo = catalogo[i]
stock = stocks[i]
disponibilidad = "disponible" if stock > 0 else "AGOTADO"
print(f"{titulo:<12} | {disponibilidad}")Aquí catalogo[i] accede al elemento en la posición i de la lista (las posiciones también empiezan en 0, como range). Este patrón de "índice manual" funciona, pero enseguida veremos con enumerate() una forma más elegante.
El bucle while: repetir mientras se cumpla una condición
El for es ideal cuando sabes qué recorrer o cuántas veces repetir. Pero a veces no lo sabes de antemano: "repite mientras quede stock", "pregunta hasta que el usuario escriba algo válido". Para eso está while:
El flujo es: evaluar la condición → si es True, ejecutar el bloque y volver a evaluar → si es False, salir del bucle.
flowchart TD
A[Inicio] --> B{¿condición?}
B -- True --> C[Ejecutar bloque]
C --> B
B -- False --> D[Continuar tras el bucle]
Ejemplo en Papyrus: simular ventas de La Odisea hasta agotar el stock.
titulo = "La Odisea"
stock = 4
while stock > 0:
print(f"Vendido un ejemplar de '{titulo}'. Quedan {stock - 1}.")
stock = stock - 1
print(f"'{titulo}' agotado. Ana debe reponer.")Salida:
Vendido un ejemplar de 'La Odisea'. Quedan 3. Vendido un ejemplar de 'La Odisea'. Quedan 2. Vendido un ejemplar de 'La Odisea'. Quedan 1. Vendido un ejemplar de 'La Odisea'. Quedan 0. 'La Odisea' agotado. Ana debe reponer.
La línea stock = stock - 1 es crucial: algo dentro del bucle debe acercar la condición a False. Si la olvidas, el stock nunca baja, la condición siempre es cierta y tienes un bucle infinito: el programa no termina jamás (si te ocurre, corta la ejecución con Ctrl+C en la terminal). Es el error clásico del while.
Por cierto, stock = stock - 1 es tan frecuente que Python ofrece una abreviatura: stock -= 1 ("resta 1 a stock"). Existen las equivalentes +=, *=, /=, etc. A partir de ahora las usaremos.
Otro uso típico del while: repetir una pregunta hasta obtener respuesta válida, una de las mejoras pendientes de ficha_libro.py:
respuesta = ""
while respuesta != "s" and respuesta != "n":
respuesta = input("¿El cliente es socio? (s/n): ")
es_socio = respuesta == "s"
print(f"Socio: {es_socio}")Mientras el usuario no escriba exactamente s o n, la pregunta se repite. Fíjate en el truco de inicializar respuesta = "" antes del bucle para que la primera evaluación de la condición sea posible (y dé True).
Contadores y acumuladores: la caja del día
Dos patrones aparecen una y otra vez con bucles, y merecen nombre propio:
- Contador: una variable que empieza en
0y suma1cada vez que ocurre algo. Responde a "¿cuántas veces...?". - Acumulador: una variable que empieza en
0(o en""para textos) y va sumando cantidades. Responde a "¿cuánto en total...?".
El ejemplo perfecto es la caja del día de Papyrus: al cerrar, Ana quiere saber cuánto ha ingresado y cuántas ventas ha hecho.
NOMBRE_TIENDA = "Papyrus"
ventas_del_dia = [12.50, 9.95, 15.90, 12.50, 9.95] # importes cobrados hoy
total_caja = 0.0 # acumulador: suma de importes
num_ventas = 0 # contador: número de operaciones
for importe in ventas_del_dia:
total_caja += importe
num_ventas += 1
print(f"Venta {num_ventas}: {importe:.2f} € (acumulado: {total_caja:.2f} €)")
print("-" * 40)
print(f"{NOMBRE_TIENDA} — cierre de caja")
print(f"Ventas realizadas: {num_ventas}")
print(f"Total del día: {total_caja:.2f} €")
print(f"Ticket medio: {total_caja / num_ventas:.2f} €")Salida:
Venta 1: 12.50 € (acumulado: 12.50 €) Venta 2: 9.95 € (acumulado: 22.45 €) Venta 3: 15.90 € (acumulado: 38.35 €) Venta 4: 12.50 € (acumulado: 50.85 €) Venta 5: 9.95 € (acumulado: 60.80 €) ---------------------------------------- Papyrus — cierre de caja Ventas realizadas: 5 Total del día: 60.80 € Ticket medio: 60.80 €...
(El ticket medio real es 12.16 €; compruébalo al ejecutar.)
La anatomía del patrón, que repetirás cientos de veces:
- Inicializar el contador/acumulador antes del bucle (si lo inicializas dentro, se pondría a cero en cada vuelta y no acumularía nada).
- Actualizar dentro del bucle (
+=). - Usar el resultado después del bucle.
Los contadores también se combinan con condicionales: contar solo lo que cumple algo.
stocks = [4, 6, 8, 0, 2]
titulos_bajo_minimo = 0
for stock in stocks:
if stock <= 3:
titulos_bajo_minimo += 1
print(f"Títulos que hay que reponer: {titulos_bajo_minimo}") # 2Bucles anidados
Igual que los if, los bucles pueden anidarse: un bucle dentro de otro. El bucle interior se ejecuta completo por cada vuelta del exterior.
Ana quiere etiquetas para todos los ejemplares físicos: una etiqueta por unidad en stock de cada título.
catalogo = ["La Odisea", "Hamlet", "El Quijote"]
stocks = [3, 2, 2] # stock reducido para abreviar la salida
for i in range(3): # bucle exterior: cada título
titulo = catalogo[i]
for unidad in range(1, stocks[i] + 1): # bucle interior: cada ejemplar
print(f"Etiqueta: {titulo} — ejemplar {unidad}/{stocks[i]}")Salida:
Etiqueta: La Odisea — ejemplar 1/3 Etiqueta: La Odisea — ejemplar 2/3 Etiqueta: La Odisea — ejemplar 3/3 Etiqueta: Hamlet — ejemplar 1/2 Etiqueta: Hamlet — ejemplar 2/2 Etiqueta: El Quijote — ejemplar 1/2 Etiqueta: El Quijote — ejemplar 2/2
Cuenta las iteraciones: el exterior da 3 vueltas y el interior 3+2+2 = 7 en total. Con bucles anidados el número de ejecuciones se multiplica rápido: dos bucles de 1000 vueltas son un millón de ejecuciones del bloque interior. Tenlo presente cuando trabajes con datos grandes.
Un clásico visual para fijar la idea, la tabla de multiplicar:
for fila in range(1, 4):
for columna in range(1, 4):
print(f"{fila * columna:>3}", end="")
print() # salto de línea al terminar cada filaSalida:
Observa el print() vacío: está indentado al nivel del bucle exterior, así que se ejecuta una vez por fila, no una vez por celda. Con bucles anidados, el nivel de indentación de cada línea determina "a qué vuelta pertenece" — revísalo siempre dos veces.
enumerate(): índice y valor a la vez
Volvamos al patrón del índice manual que usamos con las listas paralelas. Cuando recorres una lista y necesitas también la posición de cada elemento, Python ofrece algo mejor que range() + corchetes: la función enumerate(), que en cada vuelta entrega la pareja índice-valor.
catalogo = ["La Odisea", "Hamlet", "El Quijote"]
for posicion, titulo in enumerate(catalogo, start=1):
print(f"{posicion}. {titulo}")Salida:
Cómo funciona:
enumerate(catalogo)va generando parejas:(0, "La Odisea"),(1, "Hamlet"),(2, "El Quijote").- En el
forescribimos dos variables separadas por coma (posicion, titulo), y Python desempaqueta cada pareja en ellas — es la misma asignación múltiple que viste en el módulo 1. - El argumento opcional
start=1hace que la numeración empiece en 1, perfecto para listados de cara al público (por defecto empieza en 0).
Compara las dos versiones del mismo listado numerado:
# Con índice manual: funciona, pero más ruido
for i in range(len(catalogo)):
print(f"{i + 1}. {catalogo[i]}")
# Con enumerate: más claro y más pythónico
for posicion, titulo in enumerate(catalogo, start=1):
print(f"{posicion}. {titulo}")(len() devuelve la longitud de una secuencia: len(catalogo) es 3. La usaremos constantemente.) Regla práctica: si solo necesitas los valores, for x in lista; si también necesitas la posición, enumerate(); reserva range(len(...)) para casos muy concretos, como recorrer dos listas paralelas por índice.
¿for o while? Cuándo usar cada uno
| Pregunta que te haces | Bucle adecuado | Ejemplo en Papyrus |
|---|---|---|
| "Para cada elemento de esta colección..." | for |
Recorrer el catálogo |
| "Exactamente N veces" | for con range(N) |
Imprimir 3 líneas de ticket |
| "Mientras se cumpla esta condición..." | while |
Vender mientras quede stock |
| "Hasta que el usuario haga X" | while |
Repetir pregunta hasta respuesta válida |
| "No sé cuántas vueltas harán falta" | while |
Esperar una entrada correcta |
Regla de oro: si conoces de antemano la colección o el número de repeticiones, usa for; si la parada depende de algo que ocurre durante la ejecución, usa while. En la práctica, el for es mucho más frecuente en Python.
Errores Comunes y Consejos
- Esperar que
range(5)incluya el 5: genera del 0 al 4. Si quieres del 1 al 5, escriberange(1, 6). El clásico "error de uno de más/de menos" (off-by-one) nace casi siempre aquí. - El bucle infinito en
while: olvidar actualizar la variable de la condición (stock -= 1, leer un nuevoinput()...). Si tu programa "se queda colgado", es esto; interrumpe conCtrl+Cy revisa qué debería cambiar en cada vuelta. - Inicializar el acumulador dentro del bucle:
total = 0debe ir antes delfor; dentro, se reinicia en cada vuelta y el total final sería solo el último importe. - Modificar la variable de bucle de un
foresperando alterar el recorrido: hacernumero += 10dentro defor numero in range(5)no salta iteraciones; en la siguiente vueltanumerotoma el siguiente valor del rango igualmente. Para controlar los saltos usa el paso derange()o unwhile. - Indentación en bucles anidados: una línea indentada un nivel de más o de menos cambia cuántas veces se ejecuta. Si un
printsale repetido más veces de la cuenta, comprueba su nivel. - Consejo: nombra la variable de bucle en singular y la colección en plural (
for titulo in titulos). El código se lee solo. - Consejo: antes de escribir un bucle, di en voz alta qué quieres: si la frase empieza por "para cada..." es un
for; si empieza por "mientras..." es unwhile. Funciona sorprendentemente bien.
Ejercicios
Ejercicio 1: catálogo alineado con numeración
Retoma la tabla alineada de catalogo_inicial.py (módulo 1) y genérala ahora con un bucle. Con las listas titulos = ["La Odisea", "Hamlet", "El Quijote"] y precios = [12.50, 9.95, 15.90], usa enumerate() para imprimir:
(Título alineado a la izquierda en 12 caracteres, precio a la derecha en 6 con dos decimales.)
Ejercicio 2: caja del día con estadísticas
Dada ventas = [12.50, 9.95, 15.90, 15.90, 12.50, 9.95, 15.90], escribe caja_dia.py que, con un solo bucle (sin usar sum(), max() ni min(), que verás más adelante), calcule e imprima: el total de la caja, el número de ventas, la venta más alta y cuántas ventas superaron los 10 €.
Ejercicio 3: hucha de reposición
Ana aparta dinero para reponer El Quijote (15.90 € el ejemplar). Cada día puede ahorrar 4 €. Escribe un while que simule los días: acumula 4 € diarios e imprime Día N: M.MM € ahorrados hasta tener suficiente para comprar 2 ejemplares. Al final indica cuántos días hicieron falta.
Soluciones
Solución 1:
titulos = ["La Odisea", "Hamlet", "El Quijote"]
precios = [12.50, 9.95, 15.90]
for i, titulo in enumerate(titulos, start=1):
precio = precios[i - 1]
print(f"{i}. {titulo:<12}{precio:>6.2f} €")Como enumerate empieza en 1 pero las posiciones de la lista empiezan en 0, accedemos al precio con precios[i - 1]. Alternativa igual de válida: enumerate(titulos) sin start, usar precios[i] e imprimir {i + 1}.
Solución 2:
ventas = [12.50, 9.95, 15.90, 15.90, 12.50, 9.95, 15.90]
total = 0.0
num_ventas = 0
venta_maxima = 0.0
ventas_grandes = 0
for importe in ventas:
total += importe
num_ventas += 1
if importe > venta_maxima:
venta_maxima = importe
if importe > 10:
ventas_grandes += 1
print(f"Total caja: {total:.2f} €")
print(f"Número de ventas: {num_ventas}")
print(f"Venta más alta: {venta_maxima:.2f} €")
print(f"Ventas > 10 €: {ventas_grandes}")Cuatro variables, cuatro patrones: dos acumuladores/contadores incondicionales (total, num_ventas), un máximo (se actualiza solo si el importe supera al mejor visto hasta ahora) y un contador condicional. El total debe dar 92.60 € y el máximo 15.90 €.
Solución 3:
PRECIO_QUIJOTE = 15.90
AHORRO_DIARIO = 4.0
objetivo = PRECIO_QUIJOTE * 2 # 31.80 €
ahorrado = 0.0
dias = 0
while ahorrado < objetivo:
dias += 1
ahorrado += AHORRO_DIARIO
print(f"Día {dias}: {ahorrado:.2f} € ahorrados")
print(f"¡Objetivo alcanzado en {dias} días! ({ahorrado:.2f} € para {objetivo:.2f} €)")La condición ahorrado < objetivo garantiza que el bucle para en cuanto se alcanza o supera la meta: con 4 € al día se llega a 32 € el día 8. Fíjate en que incrementamos dias al principio de cada vuelta para que el mensaje muestre el día correcto.
Conclusión
Con esta lección, Papyrus ha dado un salto de escala: ya no trabajamos libro a libro, sino sobre el catálogo entero. Has aprendido el for con range() (inicio incluido, fin excluido, paso opcional), a recorrer cadenas y listas, el while para repetir mientras se cumpla una condición, los patrones de contador y acumulador con los que cerramos la caja del día, los bucles anidados y la elegante enumerate() para tener posición y valor a la vez.
Aun así, nuestros bucles son todavía algo rígidos: siempre recorren la secuencia completa, de principio a fin. ¿Y si encontramos el libro que buscábamos a mitad del catálogo y queremos parar ahí? ¿Y si queremos saltarnos los títulos agotados y seguir con el resto? ¿Y si necesitamos un menú que se repita indefinidamente hasta que Ana elija "salir"? Para todo eso existen las herramientas de control de flujo — break, continue, else en bucles y la moderna sentencia match — que son exactamente el tema de la próxima lección, donde construiremos el primer menú interactivo de Papyrus.
Curso de Programación en Python
Módulo 1: Introducción a Python
- Introducción a Python
- Configuración del Entorno de Desarrollo
- Sintaxis de Python y Tipos de Datos Básicos
- Variables y Constantes
- Entrada y Salida Básica
- Entornos Virtuales y Gestión de Paquetes
Módulo 2: Estructuras de Control
- Sentencias Condicionales
- Bucles: for y while
- Herramientas de Control de Flujo
- Comprensiones de Listas
Módulo 3: Funciones y Módulos
- Definición de Funciones
- Argumentos de Función
- Funciones Lambda
- Módulos y Paquetes
- Visión General de la Biblioteca Estándar
Módulo 4: Estructuras de Datos
Módulo 5: Programación Orientada a Objetos
Módulo 6: Manejo de Archivos
- Lectura y Escritura de Archivos
- Trabajo con Archivos CSV
- Manejo de Datos JSON
- Operaciones con Archivos y Directorios
Módulo 7: Manejo de Errores y Excepciones
- Introducción a las Excepciones
- Manejo de Excepciones
- Lanzamiento de Excepciones
- Excepciones Personalizadas
- Buenas Prácticas y Registro de Errores con logging
Módulo 8: Temas Avanzados
- Anotaciones de Tipos (type hints)
- Decoradores
- Generadores
- Administradores de Contexto
- Concurrencia: Hilos y Procesos
- Asyncio para Programación Asíncrona
Módulo 9: Pruebas y Depuración
- Introducción a las Pruebas
- Pruebas Unitarias con unittest
- Pruebas con pytest
- Desarrollo Guiado por Pruebas
- Técnicas de Depuración
- Uso de pdb para Depuración
Módulo 10: Desarrollo Web con Python
- Introducción al Desarrollo Web
- Fundamentos del Framework Flask
- Construcción de APIs REST con Flask
- Introducción a Django
- Construcción de Aplicaciones Web con Django
Módulo 11: Ciencia de Datos con Python
- Introducción a la Ciencia de Datos
- NumPy para Computación Numérica
- Pandas para Manipulación de Datos
- Matplotlib para Visualización de Datos
- Introducción al Aprendizaje Automático con scikit-learn
