Saltar al contenido
🐍 Curso de Python
/

Módulo 1 · Lección 19 de 59

Bucles anidados

Un bucle dentro de otro para recorrer filas y columnas, cuántas iteraciones salen en total y qué hace break aquí.

⏱ 12 min de lectura

Un bucle recorre una dimensión: los caracteres de un texto, los números de un rango. Para recorrer dos, como las filas y las columnas de una tabla, hace falta un bucle dentro de otro. Cualquier cosa que se organice en cuadrícula, desde una tabla de multiplicar hasta el tablero de un juego, se construye así.

Un bucle dentro de otro#

Dentro del bloque de un for puede ir cualquier instrucción, y un for es una instrucción. El interior se sangra otros cuatro espacios:

python
for fila in range(1, 4):
        for columna in range(1, 4):
                print(f"fila {fila}, columna {columna}")
Salida en pantalla
fila 1, columna 1
fila 1, columna 2
fila 1, columna 3
fila 2, columna 1
fila 2, columna 2
fila 2, columna 3
fila 3, columna 1
fila 3, columna 2
fila 3, columna 3

Lee la salida con atención, porque ahí está toda la lección: el bucle interior se ejecuta entero en cada vuelta del exterior. Mientras fila vale 1, columna recorre 1, 2 y 3 completos. Solo entonces fila pasa a 2, y el interior vuelve a empezar desde 1.

El de fuera avanza despacio; el de dentro, deprisa. Como las agujas de un reloj: la de las horas da una vuelta mientras la de los minutos da doce.

Cuántas iteraciones salen#

Se multiplican. Si el exterior da 3 vueltas y el interior 4, el bloque interior se ejecuta 3 × 4 = 12 veces:

python
vueltas = 0

for fila in range(3):
        for columna in range(4):
                vueltas += 1

print(f"El bloque interior se ha ejecutado {vueltas} veces")
Salida en pantalla
El bloque interior se ha ejecutado 12 veces

La tabla de multiplicar#

Es el ejemplo canónico, y aquí sirve además para practicar el formato de columnas de la lección de f-strings. El truco está en que cada fila se va montando en una variable de texto y se imprime de una vez al terminar el bucle interior:

tabla.py
encabezado = f"{'':>4}"
for columna in range(1, 11):
        encabezado += f"{columna:>4}"

print(encabezado)
print("-" * 44)

for fila in range(1, 11):
        linea = f"{fila:>4}"
    for columna in range(1, 11):
                linea += f"{fila * columna:>4}"
    print(linea)
Salida en pantalla
       1   2   3   4   5   6   7   8   9  10
--------------------------------------------
   1   1   2   3   4   5   6   7   8   9  10
   2   2   4   6   8  10  12  14  16  18  20
   3   3   6   9  12  15  18  21  24  27  30
   4   4   8  12  16  20  24  28  32  36  40
   5   5  10  15  20  25  30  35  40  45  50
   6   6  12  18  24  30  36  42  48  54  60
   7   7  14  21  28  35  42  49  56  63  70
   8   8  16  24  32  40  48  56  64  72  80
   9   9  18  27  36  45  54  63  72  81  90
  10  10  20  30  40  50  60  70  80  90 100

Tres detalles que hacen que la salida quede cuadrada:

  • {...:>4} reserva cuatro caracteres para cada número y lo alinea a la derecha. Cuatro bastan porque el mayor valor, 100, tiene tres cifras, y el cuarto hueco es el espacio de separación.
  • f"{'':>4}" es un hueco vacío de cuatro caracteres, para que el encabezado empiece desplazado y sus números caigan encima de la columna que les toca.
  • El separador mide 44 caracteres: 4 de la columna de la izquierda más 4 por cada una de las diez columnas.

Dibujar figuras#

Los bucles anidados son la forma clásica de practicar esto, porque obligan a razonar sobre la relación entre la fila y el número de columnas.

python
for fila in range(1, 6):
        linea = ""
    for columna in range(fila):
                linea += "*"
    print(linea)
Salida en pantalla
*
**
***
****
*****

La clave está en el range(fila) del bucle interior: el número de vueltas del interior depende de la variable del exterior. En la primera fila da una vuelta, en la segunda dos, y así. Cuando los dos bucles son independientes sale un rectángulo; cuando el interior depende del exterior, salen triángulos y escaleras.

Este ejemplo, de hecho, se puede escribir en dos líneas aprovechando que multiplicar una cadena la repite:

python
for fila in range(1, 6):
        print("*" * fila)

Y eso es lo honesto: si hay una forma sin anidar, úsala. Los bucles anidados son necesarios cuando cada celda es distinta, no cuando la fila entera es el mismo carácter repetido.

Para una pirámide sí hay que calcular dos cosas por fila, los espacios de la izquierda y los asteriscos:

python
ALTURA = 5

for fila in range(1, ALTURA + 1):
        espacios = " " * (ALTURA - fila)
    asteriscos = "*" * (2 * fila - 1)
    print(espacios + asteriscos)
Salida en pantalla
    *
   ***
  *****
 *******
*********

Y cuando cada celda depende de su posición, el anidamiento es imprescindible:

python
for fila in range(3):
        linea = ""
    for columna in range(6):
                if (fila + columna) % 2 == 0:
                        linea += "#"
        else:
                        linea += "."
    print(linea)
Salida en pantalla
#.#.#.
.#.#.#
#.#.#.

break solo sale del bucle en el que está#

Esto es lo que más confunde. Un break dentro del bucle interior termina el interior, y el exterior sigue dando vueltas como si nada:

python
for fila in range(1, 4):
        for columna in range(1, 6):
                if columna == 3:
                        break
        print(f"fila {fila}, columna {columna}")

print("Terminado.")
Salida en pantalla
fila 1, columna 1
fila 1, columna 2
fila 2, columna 1
fila 2, columna 2
fila 3, columna 1
fila 3, columna 2
Terminado.

Tres filas, y en cada una el break ha cortado el recorrido de las columnas en la tercera. El bucle exterior no se ha enterado.

Lo mismo vale para continue: afecta solo al bucle que lo contiene directamente.

Ejemplo completo#

Una tabla de precios con descuentos por volumen, que junta el anidamiento, el formato de columnas y los porcentajes:

tarifas.py
PRECIO_BASE = 24.90

encabezado = f"{'Unidades':<10}"
for descuento in range(0, 25, 5):
        encabezado += f"{descuento:>8}%"

print(encabezado)
print("-" * 55)

for unidades in range(1, 6):
        linea = f"{unidades:<10}"
    for descuento in range(0, 25, 5):
                total = PRECIO_BASE * unidades * (1 - descuento / 100)
        linea += f"{total:>9.2f}"
    print(linea)
Salida en pantalla
Unidades         0%       5%      10%      15%      20%
-------------------------------------------------------
1             24.90    23.65    22.41    21.16    19.92
2             49.80    47.31    44.82    42.33    39.84
3             74.70    70.96    67.23    63.49    59.76
4             99.60    94.62    89.64    84.66    79.68
5            124.50   118.27   112.05   105.83    99.60