Bucles en Python — teoría de for y while con la caja de la cafetería, el cambio moneda a moneda (y el while que no se cansaba de saltarse la mesa 3)

La semana antes del parcial de FP1 me puse a practicar en la cafetería de la facultad. El ejercicio era una tontería: recorrer las mesas de la 1 a la 6 y «limpiarlas», saltándome la 3 porque estaba reservada. Escribí un while, le puse un continue para saltar la mesa 3, pulsé ejecutar… y la terminal empezó a escupir la misma línea una y otra vez, el ventilador del portátil despegó y el chico de la mesa de al lado me miró como si hubiera roto algo. Esto es lo que hacía mi programa (con un freno de emergencia que le he puesto para poder enseñarte la salida sin que se cuelgue):

# Limpiar las mesas de la cafetería, saltándose la 3 (está reservada)
mesa = 1
vueltas = 0                       # freno de emergencia SOLO para poder enseñarte la salida

while mesa <= 6:
    vueltas += 1
    if vueltas > 8:               # sin este freno, el programa no pararía nunca
        print("... y así hasta que pulsas Ctrl + C")
        break
    if mesa == 3:
        print("Mesa 3 reservada, me la salto")
        continue                  # vuelve arriba SIN pasar por mesa += 1
    print(f"Limpio la mesa {mesa}")
    mesa += 1

Salida:

Limpio la mesa 1
Limpio la mesa 2
Mesa 3 reservada, me la salto
Mesa 3 reservada, me la salto
Mesa 3 reservada, me la salto
Mesa 3 reservada, me la salto
Mesa 3 reservada, me la salto
Mesa 3 reservada, me la salto
... y así hasta que pulsas Ctrl + C

¿Ves el problema? Cuando mesa vale 3, el continue vuelve arriba del bucle sin pasar por mesa += 1. La mesa sigue valiendo 3, la condición mesa <= 6 sigue siendo verdadera y el programa se queda atrapado para siempre en la misma vuelta. Sin el freno, la única salida era pulsar Ctrl + C.

Ese día entendí lo que de verdad hace un bucle, y es lo que te cuento hoy. Venimos de los condicionales en Python, donde el programa aprendió a decidir. Ahora aprenderá a repetir: los bucles (o ciclos, como se dice mucho en Latinoamérica) for y while, range(), break, continue, el «do while» que Python no tiene, los bucles anidados y los patrones que vas a escribir en todos los ejercicios de FP1. Todo con un mismo ejemplo: la caja de la cafetería de la facultad (la misma que en Operadores en Python). Y cuando acabes, en la práctica de bucles haremos tres programas completos.

¿Qué es un bucle (o ciclo) en Python?

Un bucle es un bloque de código que se repite. Sin bucles, sumar los tickets de una mañana significaría escribir una línea por ticket; con un bucle escribes la suma una vez y Python la repite tantas veces como haga falta. Cada repetición se llama vuelta (o iteración).

Python tiene dos bucles, y la diferencia entre ellos es la pregunta que hacen antes de cada vuelta:

  • for pregunta «¿queda algún elemento?». Recorre una secuencia (una lista, un texto, un range) y da una vuelta por cada elemento. Sabes de antemano cuántas vueltas habrá.
  • while pregunta «¿se sigue cumpliendo la condición?». Da vueltas mientras la condición sea True, sin saber cuántas serán.

Igual que en los condicionales, la cabecera termina en dos puntos y el cuerpo del bucle va indentado: todo lo que tenga sangría por debajo del for o del while se repite; lo que vuelve a la izquierda ya va después del bucle. Fíjate en el dibujo:

Cómo funcionan for y whileDos diagramas de flujo. El for pregunta si quedan elementos, ejecuta el cuerpo con el siguiente y vuelve. El while comprueba la condición, ejecuta el cuerpo, debe actualizar la variable de la condición y vuelve; cuando la respuesta es no, el programa sigue.for: recorre una secuenciafor ticket in tickets:¿quedan tickets?sícuerpo con ticket = 3.50,luego 1.20, luego 4.80…el for avanza solonosigue el programawhile: repite mientras se cumplawhile cola > 0:¿cola > 0?sícuerpo del buclecola -= atendidas¡esto cambia la condición!nosigue el programa
Los dos vuelven arriba tras cada vuelta; la diferencia es quién hace avanzar el bucle: el for lo hace solo, en el while lo haces tú.

Los dos bucles hacen lo mismo en esencia: comprueban, ejecutan el cuerpo y vuelven arriba. La diferencia está en la caja coral: en el while, eres tú quien tiene que cambiar algo dentro del cuerpo para que la condición acabe siendo falsa. Si no lo haces, pasa lo de mi mesa 3.

«bucle», «ciclo» y «loop» son lo mismo. En los apuntes de FP1 verás también «iteración controlada por contador» (el for) e «iteración controlada por condición» (el while).

El bucle for

El for coge una secuencia y le pone nombre a cada elemento, uno detrás de otro. En la primera vuelta, la variable del bucle vale el primer elemento; en la segunda, el segundo; y así hasta el final. Vamos con los tickets de una mañana en la cafetería:

# Tickets de la mañana en la cafetería de la facultad (en euros)
tickets = [3.50, 1.20, 4.80, 2.30, 6.10, 1.20]

total = 0                         # acumulador: empieza en 0 ANTES del bucle
grandes = 0                       # contador: tickets de más de 4 €
mayor = tickets[0]                # máximo: arranca con un ticket real, no con un número inventado

for ticket in tickets:            # ticket vale 3.50, luego 1.20, luego 4.80...
    total += ticket               # suma este ticket a lo que ya llevábamos
    if ticket > 4:
        grandes += 1              # solo cuenta los que cumplen la condición
    if ticket > mayor:
        mayor = ticket            # ha aparecido uno más alto: lo recordamos

print(f"Tickets: {len(tickets)}")
print(f"Total de la mañana: {total:.2f} €")
print(f"Media por ticket: {total / len(tickets):.2f} €")
print(f"Tickets de más de 4 €: {grandes}")
print(f"El ticket más alto: {mayor:.2f} €")

Salida:

Tickets: 6
Total de la mañana: 19.10 €
Media por ticket: 3.18 €
Tickets de más de 4 €: 2
El ticket más alto: 6.10 €

Aquí pasan tres cosas a la vez dentro del mismo bucle, y las tres son patrones que vas a repetir mil veces:

  • Acumulador (total): empieza en 0 antes del bucle, se actualiza dentro (total += ticket) y se usa después.
  • Contador (grandes): también empieza en 0, pero solo suma 1 cuando se cumple una condición.
  • Máximo (mayor): empieza con un valor real de la lista y se sustituye cada vez que aparece uno más alto.

Para que nadie tenga que imaginarse nada, esta es la traza vuelta a vuelta (los valores son los que hay después de cada vuelta):

Vueltatickettotalgrandesmayor
antes—003.50
13.503.5003.50
21.204.7003.50
34.809.5014.80
42.3011.8014.80
56.1017.9026.10
61.2019.1026.10

Comprobación a mano: 3,50 + 1,20 + 4,80 + 2,30 + 6,10 + 1,20 = 19,10 €, y 19,10 / 6 = 3,18 €. Los tickets de más de 4 € son dos (4,80 y 6,10). Cuadra con la salida.

Poner total = 0 dentro del bucle. Entonces se reinicia en cada vuelta y al final solo queda el último ticket. Lo verás en directo en los ejercicios, en «¿Qué falla aquí?».

El nombre de la variable del bucle lo eliges tú. for ticket in tickets se lee casi como una frase («para cada ticket de los tickets»). Usa el singular de la lista y tu código se explicará solo.

El cambio moneda a moneda, ahora con un for

En los ejercicios de Operadores calculamos el cambio de la caja con doce líneas casi iguales, una por billete o moneda (b50, resto = divmod(resto, 5000), b20, resto = divmod(resto, 2000)…). Te prometí que con bucles se hacía mejor. Aquí está: los doce valores van en una lista y una sola línea de divmod se repite doce veces.

# El cambio moneda a moneda (el ejercicio 5 de Operadores), ahora con un bucle
importe = 37.43
entregado = 50.00
resto = round((entregado - importe) * 100)   # céntimos ENTEROS: así no hay errores de float
total_cent = resto                           # lo guardamos para el título
valores = [5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1]   # billetes y monedas, en céntimos
piezas = 0

print(f"--- Cambio: {total_cent // 100},{total_cent % 100:02d} € ---")
for valor in valores:                        # de mayor a menor: así se usan las piezas más grandes
    cuantas, resto = divmod(resto, valor)    # cuántas caben y lo que sobra para la siguiente
    if cuantas > 0:                          # solo enseñamos lo que de verdad se entrega
        if valor >= 100:
            nombre = f"{valor // 100} €"
        else:
            nombre = f"{valor} cént."
        print(f"{cuantas} x {nombre}")
        piezas += cuantas
print(f"Piezas: {piezas} | sobran: {resto} céntimos")

Salida:

--- Cambio: 12,57 € ---
1 x 10 €
1 x 2 €
1 x 50 cént.
1 x 5 cént.
1 x 2 cént.
Piezas: 5 | sobran: 0 céntimos

Vamos al corazón del programa, el bucle:

for valor in valores:                        # de mayor a menor: así se usan las piezas más grandes
    cuantas, resto = divmod(resto, valor)    # cuántas caben y lo que sobra para la siguiente
    if cuantas > 0:                          # solo enseñamos lo que de verdad se entrega
        if valor >= 100:
            nombre = f"{valor // 100} €"
        else:
            nombre = f"{valor} cént."
        print(f"{cuantas} x {nombre}")
        piezas += cuantas

En cada vuelta, valor es un billete o una moneda (en céntimos) y divmod(resto, valor) responde a dos preguntas a la vez: cuántas piezas de ese valor caben en lo que queda (cuantas) y cuánto sobra para las siguientes (resto). Como resto se sobrescribe en cada vuelta, la siguiente moneda trabaja solo con lo que ha sobrado. Esta es la traza de las vueltas en las que pasa algo:

valorresto antesdivmodcuantasresto después
5000, 20001257(0, 1257)01257
10001257(1, 257)1257
500257(0, 257)0257
200257(1, 57)157
10057(0, 57)057
5057(1, 7)17
20, 107(0, 7)07
57(1, 2)12
22(1, 0)10
10(0, 0)00

Comprobación: 10 € + 2 € + 0,50 € + 0,05 € + 0,02 € = 12,57 €, que es 50,00 − 37,43. Cinco piezas y no sobra nada.

El orden de la lista importa. Al ir de mayor a menor, cada vuelta usa la pieza más grande posible, y por eso salen pocas piezas. Si la lista fuera de menor a mayor, la primera vuelta (1 céntimo) se lo llevaría todo: 1257 monedas de un céntimo.

El bucle for con range()

Muchas veces no tienes una lista, sino un número: «repite esto 6 veces», «recorre las mesas de la 1 a la 10». Para eso está range(), que genera números enteros. Tiene tres formas:

  • range(fin): del 0 al fin − 1.
  • range(inicio, fin): del inicio al fin − 1.
  • range(inicio, fin, paso): de paso en paso; con un paso negativo, cuenta hacia atrás.

Lo mejor para entenderlo es verlo. list() convierte el rango en una lista solo para imprimirlo (en un for no hace falta):

# list() solo sirve para VER los números que genera cada range
print("range(5)        ->", list(range(5)))
print("range(1, 6)     ->", list(range(1, 6)))
print("range(0, 11, 2) ->", list(range(0, 11, 2)))
print("range(5, 0, -1) ->", list(range(5, 0, -1)))
print("range(5, 1)     ->", list(range(5, 1)))   # vacío: para bajar hace falta un paso negativo

Salida:

range(5)        -> [0, 1, 2, 3, 4]
range(1, 6)     -> [1, 2, 3, 4, 5]
range(0, 11, 2) -> [0, 2, 4, 6, 8, 10]
range(5, 0, -1) -> [5, 4, 3, 2, 1]
range(5, 1)     -> []

La regla que más fallos evita: el fin nunca entra. range(1, 6) llega al 5, no al 6. Y el último caso sale vacío porque, sin paso negativo, range solo sabe subir: del 5 al 1 «subiendo» no hay ningún número, así que un for sobre ese rango no daría ni una vuelta (sin error, simplemente no hace nada). El dibujo lo deja claro:

Qué números genera rangeTres rectas numéricas del 0 al 11. range(1, 11) marca del 1 al 10 y deja fuera el 11. range(10, 0, -1) marca del 10 al 1 y deja fuera el 0. range(0, 11, 2) marca 0, 2, 4, 6, 8 y 10.range(1, 11) → del 1 al 1001234567891011fin: no entrarange(10, 0, -1) → del 10 al 1, hacia atrás0fin: no entra1234567891011range(0, 11, 2) → de dos en dos01234567891011
El segundo número de range es una frontera: el bucle se para justo ANTES de llegar a él.

Querer del 1 al 10 y escribir range(1, 10). Te faltará el 10. Si quieres llegar a N, el fin es N + 1: range(1, 11). Es el famoso «error de uno» (off-by-one), y es responsable de la mitad de los fallos de bucles de FP1.

El bucle for para generar tablas

Un uso clásico de range es generar tablas, como la de multiplicar. En la cafetería, la tabla de precios para la pizarra:

# Tabla de precios para la pizarra: de 1 a 6 cafés a 1,20 € cada uno
precio = 1.20

print("Cafés | Total")
for cafes in range(1, 7):         # 7 y no 6: el fin de range NO entra
    print(f"  {cafes}   | {cafes * precio:.2f} €")

Salida:

Cafés | Total
  1   | 1.20 €
  2   | 2.40 €
  3   | 3.60 €
  4   | 4.80 €
  5   | 6.00 €
  6   | 7.20 €

cafes vale 1, 2, 3, 4, 5 y 6 (el 7 no entra), y en cada vuelta se calcula cafes * precio. Por ejemplo, 3 × 1,20 = 3,60 €. El :.2f redondea a dos decimales al mostrar, por eso no ves el 3.5999999999999996 que da el float de verdad (si no te suena, lo explicamos en variables y tipos de datos).

El bucle while

El while no recorre nada: repite mientras su condición sea verdadera. Antes de cada vuelta evalúa la condición; si es True, ejecuta el cuerpo y vuelve a preguntar; si es False, sigue con lo que haya debajo. La cola de la cafetería a las 10:30 es un caso perfecto:

# La cola de la cafetería a las 10:30: cada minuto se atiende a 2 personas
cola = 7                          # personas esperando
minuto = 0

while cola > 0:                   # mientras quede alguien en la cola
    minuto += 1
    atendidas = min(2, cola)      # en la última vuelta puede quedar solo 1
    cola -= atendidas             # la condición depende de cola, y AQUÍ cambia
    print(f"Minuto {minuto}: atiendo a {atendidas}, quedan {cola}")

print(f"Cola vacía en {minuto} minutos")

Salida:

Minuto 1: atiendo a 2, quedan 5
Minuto 2: atiendo a 2, quedan 3
Minuto 3: atiendo a 2, quedan 1
Minuto 4: atiendo a 1, quedan 0
Cola vacía en 4 minutos

La condición es cola > 0, y lo que la hace cambiar es cola -= atendidas. Así va la traza:

Vuelta¿cola > 0? (antes)atendidascola después
17 > 0 → True25
25 > 0 → True23
33 > 0 → True21
41 > 0 → True10
—0 > 0 → False—(sale)

¿Por qué min(2, cola) y no un 2 fijo? Porque en la última vuelta solo queda una persona. Con un 2 fijo, la cola pasaría de 1 a −1, la condición cola > 0 acabaría igual (−1 no es mayor que 0), pero el programa diría «atiendo a 2» con una sola persona delante. min devuelve el menor de los dos: atiende a 2 si hay 2 o más, y a las que queden si no.

Antes de escribir un while, responde a tres preguntas: ¿qué variable mira la condición?, ¿qué valor tiene antes de la primera vuelta?, ¿qué línea del cuerpo la acerca al final? Si no encuentras la tercera, tienes un bucle infinito.

El error más peligroso con while: el bucle infinito

Si nada dentro del cuerpo cambia la condición, el while no termina nunca. Es lo que me pasó en el gancho. Las tres formas más habituales de conseguirlo:

  1. Olvidar actualizar la variable: un while cola > 0: sin el cola -= ….
  2. Un continue que se salta la actualización: mi mesa 3. El continue vuelve arriba antes de llegar al mesa += 1.
  3. Actualizar en el sentido equivocado: cola += 1 en lugar de cola -= 1. La cola crece y nunca llega a 0.

Si te pasa, pulsa Ctrl + C en la terminal (en VS Code, haz clic primero en la terminal): Python lanza un KeyboardInterrupt y el programa se para. Y el arreglo del gancho: o mueves el mesa += 1 antes del continue, o, mejor, usas un for, que avanza solo:

# La misma tarea con for: el for avanza solo, así que no hay += 1 que olvidar
for mesa in range(1, 7):          # mesas 1 a 6 (el 7 no entra)
    if mesa == 3:
        print("Mesa 3 reservada, me la salto")
        continue                  # aquí continue es seguro: el for pasa a la mesa 4
    print(f"Limpio la mesa {mesa}")

Salida:

Limpio la mesa 1
Limpio la mesa 2
Mesa 3 reservada, me la salto
Limpio la mesa 4
Limpio la mesa 5
Limpio la mesa 6

Un continue dentro de un while antes de la línea que actualiza el contador. En un for el continue es inofensivo (el for pasa solo al siguiente elemento); en un while, si salta la actualización, el bucle no avanza.

¿Cuándo usar for y cuándo usar while?

Es la pregunta del millón, y la respuesta es corta: ¿sabes cuántas vueltas habrá antes de empezar? Si sí (los tickets de una lista, las mesas de la 1 a la 6, los 12 billetes y monedas), for. Si no (hasta que la cola se vacíe, hasta que el cliente teclee un importe válido), while.

SituaciónBuclePor qué
Recorrer los tickets de una listafor ticket in tickets:Hay tantas vueltas como tickets
Repetir algo N vecesfor i in range(N):El número de vueltas es N
Atender hasta que la cola esté vacíawhile cola > 0:No sabes cuántos minutos tardará
Pedir un dato hasta que sea válidowhile True: + breakNo sabes cuántas veces se equivocará
Buscar algo y parar al encontrarlofor + breakRecorres una secuencia, pero puedes salir antes

Todo lo que hace un for se puede hacer con un while (llevando tú el contador), pero no al revés. El ejemplo de tus apuntes de FP1 es el algoritmo de Euclides para el máximo común divisor. En la cafetería: hay 84 croissants y 36 zumos y queremos repartirlos en bandejas iguales, sin que sobre nada. ¿Cuántas bandejas como máximo?

# 84 croissants y 36 zumos en bandejas iguales, sin que sobre nada: ¿cuántas bandejas?
# Es el máximo común divisor (algoritmo de Euclides, el de los apuntes de FP1)
a = 84
b = 36
vuelta = 0

while b > 0:                      # no sabemos cuántas vueltas dará: por eso while
    vuelta += 1
    resto = a % b
    print(f"Vuelta {vuelta}: {a} % {b} = {resto}")
    a = b                         # el divisor pasa a ser el dividendo
    b = resto                     # y el resto, el nuevo divisor

print(f"MCD = {a}: {a} bandejas con {84 // a} croissants y {36 // a} zumos")

Salida:

Vuelta 1: 84 % 36 = 12
Vuelta 2: 36 % 12 = 0
MCD = 12: 12 bandejas con 7 croissants y 3 zumos
Vueltaaba % ba, b después
184361236, 12
23612012, 0
—120—b > 0 es False: sale

¿Cuántas vueltas daría con 1071 y 462? ¿Y con 89 y 55? No lo sabes hasta que lo ejecutas: calcularlo costaría lo mismo que hacer el algoritmo. Por eso aquí for no tiene sentido y while sí. Comprobación: 12 × 7 = 84 y 12 × 3 = 36.

break: salir del bucle antes de tiempo

break termina el bucle en el acto, aunque queden elementos o la condición siga siendo verdadera. El programa sigue en la primera línea de después del bucle. Sirve sobre todo para búsquedas: en cuanto encuentras lo que buscas, ¿para qué seguir mirando?

# ¿Cuál es el primer ticket de más de 5 €? En cuanto lo encuentro, dejo de mirar
tickets = [3.50, 1.20, 4.80, 6.10, 2.30, 7.40]

for ticket in tickets:
    print(f"Miro {ticket:.2f} €")
    if ticket > 5:
        print(f"Encontrado: {ticket:.2f} €")
        break                     # sale del for: 2.30 y 7.40 ni se miran

print("Fin de la búsqueda")

Salida:

Miro 3.50 €
Miro 1.20 €
Miro 4.80 €
Miro 6.10 €
Encontrado: 6.10 €
Fin de la búsqueda

El bucle mira 3,50, 1,20 y 4,80 (ninguno pasa de 5), encuentra 6,10 y el break lo corta: 2,30 y 7,40 no se llegan a mirar, ni siquiera se imprime su «Miro». Fíjate en que 7,40 también pasa de 5 €, pero no aparece: el break se queda con el primero.

continue: saltar una vuelta

continue no sale del bucle: abandona la vuelta actual y pasa directamente a la siguiente. Lo que hay debajo del continue no se ejecuta en esa vuelta. En la caja, los tickets anulados se marcan con 0 y no hay que sumarlos:

# Sumar solo los tickets válidos (0 = ticket anulado)
tickets = [3.50, 0, 4.80, 0, 2.30]
total = 0

for ticket in tickets:
    if ticket == 0:
        continue                  # se salta el resto de ESTA vuelta y pasa al siguiente ticket
    total += ticket
    print(f"Sumo {ticket:.2f} € y llevo {total:.2f} €")

print(f"Total válido: {total:.2f} €")

Salida:

Sumo 3.50 € y llevo 3.50 €
Sumo 4.80 € y llevo 8.30 €
Sumo 2.30 € y llevo 10.60 €
Total válido: 10.60 €

En las vueltas 2 y 4 (ticket == 0), el continue salta el total += ticket y el print, por eso solo hay tres líneas de «Sumo». Comprobación: 3,50 + 4,80 + 2,30 = 10,60 €.

continue es útil para «filtrar» al principio del cuerpo: descartas los casos que no te interesan y el resto del código se queda sin tanta sangría. Lo mismo se puede escribir con un if ticket != 0: que envuelva el resto; elige lo que se lea mejor.

break vs continue: la diferencia clave

Las dos palabras interrumpen la vuelta actual, pero una se va del bucle y la otra se queda. Con el mismo bucle y la misma mesa 4, mira la diferencia:

# La mesa 4 tiene un problema: ¿qué cambia entre break y continue?
print("Con break:   ", end="")
for mesa in range(1, 7):
    if mesa == 4:
        break                     # se acaba el bucle: 5 y 6 no llegan
    print(mesa, end=" ")
print()

print("Con continue:", end=" ")
for mesa in range(1, 7):
    if mesa == 4:
        continue                  # solo se salta la 4: 5 y 6 sí llegan
    print(mesa, end=" ")
print()

Salida:

Con break:   1 2 3 
Con continue: 1 2 3 5 6 
¿Termina la vuelta actual?¿Termina el bucle?Después, Python va a…
breakSíSíla primera línea de después del bucle
continueSíNola siguiente vuelta (siguiente elemento o comprobar la condición)

El print() vacío del final de cada bucle solo hace el salto de línea: dentro del bucle imprimimos con end=" " para que los números queden seguidos en la misma línea.

¿Existe do while en Python? while True y break

En otros lenguajes (C, Java) existe el do … while: un bucle que ejecuta el cuerpo primero y comprueba la condición después, así que siempre da al menos una vuelta. Python no tiene do while. Lo que se hace en su lugar es un while True: (un bucle que, por sí solo, no termina nunca) con un break dentro en el momento en que se cumple la condición de salida. Es justo lo que pasa con el datáfono de la caja, que pide el importe hasta que es válido.

Validar entradas con while

Primero, la forma clásica. Para poder enseñarte la salida real sin input(), la lista tecleados hace de lo que va tecleando el cliente, en orden:

# Validar un importe: se pide otra vez mientras no sea válido
# (sin input(): la lista hace de lo que va tecleando el cliente, en orden)
tecleados = [-2.0, 0.0, 4.80]
i = 0

importe = tecleados[i]            # primera lectura, ANTES del bucle
while importe <= 0:               # mientras el dato no valga...
    print(f"{importe:.2f} € no es válido, teclea otra vez")
    i += 1
    importe = tecleados[i]        # ...se vuelve a leer DENTRO del bucle

print(f"Cobro {importe:.2f} €")

Salida:

-2.00 € no es válido, teclea otra vez
0.00 € no es válido, teclea otra vez
Cobro 4.80 €

Funciona, pero fíjate en que la lectura (importe = tecleados[i]) está dos veces: una antes del bucle, para tener algo que comprobar, y otra dentro, para volver a pedir. Con input() sería igual: un input() antes del while y otro dentro. Con while True la lectura aparece una sola vez:

# Lo mismo con while True + break: el "do while" de Python
tecleados = [-2.0, 0.0, 4.80]
i = 0

while True:                       # la condición no para nunca: la salida es el break
    importe = tecleados[i]        # una sola lectura, dentro del bucle
    i += 1
    if importe > 0:
        break                     # dato válido: salimos
    print(f"{importe:.2f} € no es válido, teclea otra vez")

print(f"Cobro {importe:.2f} € (lecturas: {i})")

Salida:

-2.00 € no es válido, teclea otra vez
0.00 € no es válido, teclea otra vez
Cobro 4.80 € (lecturas: 3)
Vueltaimporte¿importe > 0?Qué pasa
1-2.0Noimprime el aviso y vuelve arriba
20.0Noimprime el aviso y vuelve arriba
34.8Síbreak: sale del bucle

Así se responde también a «¿cómo terminar un while en Python?»: o la condición se vuelve falsa, o un break lo corta desde dentro (o Ctrl + C, si algo ha ido mal).

Un while True: sin ningún break alcanzable. Comprueba siempre que hay un camino que llega al break: si el if que lo protege nunca puede ser verdadero, has escrito un bucle infinito a propósito.

En un programa real con input(), el patrón queda así: while True: → texto = input("Importe: ") → si es válido, break → si no, aviso. Lo verás en los programas de FP1 que piden datos al usuario.

Bucles anidados: un bucle dentro de otro

Un bucle anidado es un bucle dentro del cuerpo de otro. La regla que lo explica todo: el bucle de dentro se ejecuta completo en cada vuelta del de fuera. Antes de abrir la cafetería hay que revisar las sillas: 3 mesas con 4 sillas cada una.

# Repasar las sillas: 3 mesas con 4 sillas cada una
paso = 0

for mesa in range(1, 4):          # bucle exterior: mesas 1, 2 y 3
    for silla in range(1, 5):     # bucle interior: sillas 1 a 4 de ESA mesa
        paso += 1
        print(f"Paso {paso:2}: mesa {mesa}, silla {silla}")

print(f"Total: {paso} sillas revisadas (3 x 4)")

Salida:

Paso  1: mesa 1, silla 1
Paso  2: mesa 1, silla 2
Paso  3: mesa 1, silla 3
Paso  4: mesa 1, silla 4
Paso  5: mesa 2, silla 1
Paso  6: mesa 2, silla 2
Paso  7: mesa 2, silla 3
Paso  8: mesa 2, silla 4
Paso  9: mesa 3, silla 1
Paso 10: mesa 3, silla 2
Paso 11: mesa 3, silla 3
Paso 12: mesa 3, silla 4
Total: 12 sillas revisadas (3 x 4)

mesa vale 1 y, con ella fija, silla recorre 1, 2, 3 y 4. Cuando el bucle de dentro termina, el de fuera da su segunda vuelta (mesa = 2) y el de dentro vuelve a empezar desde la silla 1. Por eso hay 3 × 4 = 12 pasos. Mira el orden en el dibujo:

Orden de un bucle anidadoUna cuadrícula de 3 mesas por 4 sillas con el número de paso en cada casilla: la mesa 1 ocupa los pasos 1 a 4, la mesa 2 los pasos 5 a 8 y la mesa 3 los pasos 9 a 12.for mesa in range(1, 4): → for silla in range(1, 5):silla 1silla 2silla 3silla 4mesa 1paso 1paso 2paso 3paso 4mesa 2paso 5paso 6paso 7paso 8mesa 3paso 9paso 10paso 11paso 12exterior: 3 vueltas · interior: 4 vueltas por cada una · 3 × 4 = 12 pasos
El bucle de dentro termina su fila entera antes de que el de fuera pase a la siguiente mesa.

El {paso:2} del print reserva dos huecos para el número, por eso «Paso 1» lleva un espacio de más y la columna queda alineada con «Paso 10».

Patrón de estrella con bucles anidados

El triángulo de asteriscos es un clásico de examen porque obliga a entender que el bucle de dentro puede depender del de fuera:

# El triángulo de asteriscos, clásico de examen
filas = 5

for fila in range(1, filas + 1):  # fila vale 1, 2, 3, 4 y 5
    for _ in range(fila):         # tantas vueltas como el número de la fila
        print("*", end="")        # end="" deja los asteriscos en la misma línea
    print()                       # al acabar la fila, salto de línea

Salida:

*
**
***
****
*****

En la fila 1, el bucle de dentro da 1 vuelta; en la fila 2, 2 vueltas… porque su rango es range(fila). El end="" evita el salto de línea después de cada asterisco, y el print() del final de cada fila (fuera del bucle de dentro, pero dentro del de fuera: mira la sangría) hace el salto. El _ como nombre de variable es una convención: «necesito dar vueltas, pero no uso el valor».

En Python, "*" * fila repite el texto: "*" * 3 es "***". El triángulo se puede hacer con un solo bucle y print("*" * fila). En el examen, de todas formas, suelen pedirte la versión con dos bucles: practica las dos.

break en bucles anidados: solo sale del de dentro

Esto lo dicen tus apuntes con una frase que conviene subrayar: break termina solo el bucle más interno en el que está. El de fuera sigue tan tranquilo. Revisemos las sillas buscando la primera rota de cada mesa (las rotas son la silla 3 de la mesa 1 y la silla 2 de la mesa 3):

# Buscar la primera silla rota de cada mesa (rotas: mesa 1 silla 3 y mesa 3 silla 2)
for mesa in range(1, 4):
    for silla in range(1, 5):
        if (mesa == 1 and silla == 3) or (mesa == 3 and silla == 2):
            print(f"Mesa {mesa}: silla {silla} rota, paso a la siguiente mesa")
            break                 # sale SOLO del bucle de las sillas
        print(f"Mesa {mesa}, silla {silla}: bien")

print("Revisión terminada")

Salida:

Mesa 1, silla 1: bien
Mesa 1, silla 2: bien
Mesa 1: silla 3 rota, paso a la siguiente mesa
Mesa 2, silla 1: bien
Mesa 2, silla 2: bien
Mesa 2, silla 3: bien
Mesa 2, silla 4: bien
Mesa 3, silla 1: bien
Mesa 3: silla 2 rota, paso a la siguiente mesa
Revisión terminada

En la mesa 1, el break corta el bucle de las sillas al llegar a la 3 (la silla 4 no se revisa), pero el bucle de las mesas sigue con la mesa 2, que se revisa entera. Aquí es justo lo que queremos: «la primera silla rota de cada mesa«. Si quisieras parar todo al encontrar la primera rota, necesitarías una variable bandera; lo resolvemos en los ejercicios.

El acumulador: el patrón esencial con bucles

Si repasas este artículo, casi todos los programas tienen la misma forma: una variable que se prepara antes del bucle, se actualiza dentro y se usa después. Es el acumulador, y su valor inicial depende de la operación:

Quiero…Antes del bucleDentro del bucleEjemplo de hoy
Sumartotal = 0total += tickettotal de la mañana
Contargrandes = 0grandes += 1 (dentro de un if)tickets de más de 4 €
Multiplicarproducto = 1producto *= ifactorial (en la práctica)
Buscar el máximomayor = tickets[0]if ticket > mayor: mayor = ticketticket más alto
Construir un textotexto = ""texto += …el «1 x 2 x 3» de la práctica

¿Por qué el producto empieza en 1 y no en 0? Porque 0 × lo que sea = 0: el resultado sería siempre 0. El 1 es el «neutro» de la multiplicación, igual que el 0 lo es de la suma.

Errores típicos con bucles

while sin actualizar la variable de la condición, o con un continue que se salta la actualización. Bucle infinito: Ctrl + C.

range(1, 10) cuando querías llegar al 10. El fin nunca entra: range(1, 11).

Inicializar el acumulador dentro del bucle (se reinicia en cada vuelta) o no inicializarlo (NameError: name 'total' is not defined).

Esperar que break salga de todos los bucles anidados. Solo sale del más interno.

Sangría equivocada. Un print con la misma sangría que el cuerpo se repite en cada vuelta; uno a la altura del for se ejecuta una sola vez, al final. Si la salida sale repetida (o no sale), mira primero las sangrías.

Míralo paso a paso en Python Tutor

pythontutor.com ejecuta tu código línea a línea y dibuja cada variable con su valor en ese momento. Con los bucles es oro, porque ves la flecha subir al principio en cada vuelta. Elige Python 3, pega este programa (combina continue y break en el mismo bucle) y pulsa Visualize Execution:

# Para Python Tutor: continue y break en el mismo bucle
tickets = [3.50, 0, 4.80, 6.10, 2.30]
total = 0

for ticket in tickets:
    if ticket == 0:
        continue                  # ticket anulado: a la siguiente vuelta
    total += ticket
    if total > 10:
        break                     # caja llena por hoy: se acabó
print(round(total, 2))

Salida:

14.4

Qué observar paso a paso:

  1. En la primera vuelta, ticket vale 3.5 y total pasa de 0 a 3.5. Fíjate en que la flecha roja vuelve a la línea del for después del if total > 10.
  2. En la segunda vuelta, ticket vale 0: la flecha salta del continue directamente al for, sin pasar por total += ticket. total sigue en 3.5.
  3. En la tercera, total llega a 8.3 (3,50 + 4,80), que todavía no pasa de 10.
  4. En la cuarta, total llega a 14.4 y el break manda la flecha fuera del bucle: el ticket de 2.30 nunca llega a ser ticket.
  5. Al final se imprime 14.4. Comprobación: 3,50 + 4,80 + 6,10 = 14,40. El round(total, 2) está por si acaso: con otros tickets, la suma de floats podría dejar algo como 14.399999999999999.

Resumen

ConceptoPara quéEjemplo
for x in secuencia:Una vuelta por cada elementofor ticket in tickets:
range(inicio, fin, paso)Generar enteros; el fin no entrarange(1, 11) → 1…10
while condición:Repetir mientras sea Truewhile cola > 0:
Bucle infinitoLa condición nunca cambiaCtrl + C para parar
breakSalir del bucle (solo del más interno)buscar el primer ticket de más de 5 €
continueSaltar el resto de la vueltano sumar tickets anulados
while True + breakEl «do while» de Pythonel datáfono
Bucles anidadosEl de dentro, completo en cada vuelta del de fuera3 mesas × 4 sillas
AcumuladorAntes, dentro, despuéstotal = 0 → total += t

Tres ideas para llevarte:

  1. Antes de escribir un bucle, pregúntate si sabes cuántas vueltas dará: si lo sabes, for; si no, while.
  2. En todo while, localiza la línea que acerca la condición a False. Si no existe (o un continue se la salta), tienes un bucle infinito.
  3. range se para justo antes del fin, y break solo sale del bucle en el que está. Esas dos frases evitan la mayoría de los fallos de examen.

En la práctica de bucles en Python los usamos en tres programas completos: el PIN de la caja, las formas de ordenar la cola y el horno de croissants. Y cuando domines los bucles, el siguiente paso son las funciones en Python: meter un bucle dentro de una función con nombre para no volver a escribirlo nunca.

Siguiente paso: prácticaBucles en Python — práctica con el PIN de la caja, las formas de ordenar la cola y el horno de croissants (y el intento que se contaba demasiado tarde)

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *