Bucle while de Python

El bucle while de Python hace que se ejecute un bloque de código repetidamente mientras una condición sea verdadera. En Python, los bucles while se utilizan principalmente cuando el número de iteraciones necesarias no viene determinado de antemano. A continuación, descubre cómo funciona el bucle while en Python.

Consejo

Aprende a escribir código Python por tu cuenta con nuestro tutorial de Python.

¿Qué es el bucle while en Python?

El bucle while en Python es una estructura de control. Las estructuras de control determinan qué ruta de código se sigue en el momento de la ejecución. Los bucles se utilizan generalmente para ejecutar repetidamente un mismo bloque de código. A continuación, te mostramos un resumen de las estructuras de control más importantes en Python:

Estructura de control en Python Explicación
Bifurcación if else Ejecuta el bloque de código una vez si la condición es verdadera.
Bucle while de Python Ejecuta el bloque de código repetidamente mientras la condición sea verdadera.
Bucle For de Python Itera sobre los elementos de una colección, ejecutando un bloque de código para cada elemento.

Los programadores menos experimentados no suelen conocer los bucles e intentan emular su funcionamiento. En lugar de definir una estructura de datos con varios elementos y procesarla con un bucle, definen una variable independiente para cada conjunto de datos. Esto supone que se duplique el código para procesar los conjuntos de datos y que se obtenga como resultado un código ineficiente. A continuación, se muestra un ejemplo en el que se han utilizado tres personas con sus nombres y edades:

person1 = ('Jim', 17)
person2 = ('Jack', 42)
person3 = ('John', 63)
print(f"{person1[0]}, {person1[1]} years old")
print(f"{person2[0]}, {person2[1]} years old")
print(f"{person3[0]}, {person3[1]} years old")

Además de la indeseable duplicación de código, este enfoque plantea un problema logístico: si el número de conjuntos de datos no se conoce hasta el momento de la ejecución, las variables individuales correspondientes no pueden definirse de antemano. Para resolver este problema, se utilizan colecciones de elementos y bucles para procesar esas colecciones.

Si se sabe cuántas repeticiones se necesitan en el momento de la ejecución, el bucle for de Python ofrece el mejor resultado. Como ejemplo, se redacta el código de tal manera que muestre los nombres y edades de tres personas utilizando un bucle for. El código funciona sin duplicidades y contiene solo dos variables, independientemente del número de conjuntos de datos:

people = ('Jim', 'Jack', 'John')
ages = (17, 42, 63)
for person, age in zip(people, ages):
    print(f"{person}, {age} years old")

A diferencia del bucle for, el bucle while se utiliza en Python cuando no se sabe cuántas iteraciones van a hacer falta. Un ejemplo claro es el intercambio de mensajes a través de una conexión abierta. Mientras la conexión esté abierta, los mensajes se procesan. Una sentencia if en el cuerpo del bucle evalúa una señal y termina la conexión si así desea:

while connection_open():
    print('Ready to receive')
    process_messages()
    if should_close_connection():
        close_connection()
# once loop terminates
print('Connection was closed')

Además, los bucles while se utilizan para efectuar un número de repeticiones básicamente ilimitado. Ejemplos conocidos son los cajeros automáticos, el prompt de Linux y el “Read Eval Print Loop” (REPL) de Python. A continuación, te mostramos una representación esquemática de una implementación de REPL a través de un bucle while infinito:

# Loop
while True:
    # Read user input
    user_input = read_input()
    # Evaluate input and produce result
    result = eval(user_input)
    # Print result
    print(result)

¿Cómo funciona el bucle while en Python?

El bucle while de Python funciona de forma similar a la bifurcación if else de Python. Ambas estructuras de control constan de dos partes:

  1. una condición de ser evaluada
  2. un cuerpo con directrices

La diferencia entre ambos es la frecuencia con la que se ejecuta el cuerpo. El cuerpo de una sentencia if se ejecuta una única vez:

if condition:
    run_once()

A diferencia de la sentencia if, el cuerpo del bucle while en Python se ejecuta varias veces si es necesario:

while condition:
    run_again()

Se presenta el patrón general al ejecutar un bucle while de Python:

  1. Se evalúa la condición.
  2. Si la condición es verdadera, se ejecuta el cuerpo del bucle.
  3. La condición se evalúa de nuevo:
    1. Si la condición se sigue cumpliendo, se repite este proceso.
    2. Si la condición es falsa, el bucle termina.

Al igual que la sentencia if, un bucle while en Python puede tener un bloque else opcional. El bloque else se ejecuta una vez si la condición es o se convierte en falsa:

while False:
    # this code doesn't loop
    never_runs()
else:
    # instead, this code runs once
    runs_once()

¿En qué se diferencian los bucles for y while en Python?

El bucle while también tiene relación con el bucle for en Python. Ambos ejecutan un mismo bloque de código repetidamente, proceso también conocido como “iterar”. La principal diferencia radica en el número de iteraciones que efectúa cada uno.

En Python, los bucles for se utilizan principalmente para iterar sobre los elementos de una colección. El número máximo de iteraciones viene limitado por la longitud de la colección. A continuación, se itera sobre las letras de la palabra “Python” y se emite cada letra individualmente:

for letter in 'Python':
    print(letter)

El bucle while de Python no se especializa en iterar colecciones y puede utilizarse de manera flexible. En general, el bucle while sienta la base del bucle. La utilidad que ofrece un bucle for también puede conseguirse mediante un bucle while, pero no al revés.

A continuación, algunos ejemplos. Se imita la función de un bucle for convencional con una variable de bucle numérico, como es el caso del bucle while de Python. Para ello, se define una variable contadora fuera del bucle y se aumenta su valor dentro del cuerpo de este:

counter = 0
limit = 10
while counter < limit:
    print(counter)
    counter += 1

El bucle for equivalente es más corto y directo:

for counter in range(10):
    print(counter)

Si se utiliza un bucle while para iterar sobre las letras de una palabra, este se comporta de la misma manera. Se usa un iterador y la función next(). Si el iterador se agota, se devuelve None en lugar de una letra y, a su misma vez, el bucle termina. El código resultante es mucho más complejo que el bucle for equivalente. Queda demostrado que el bucle while en Python no es la herramienta óptima para resolver este problema:

word = 'Python'
letters = iter(word)
letter = ''
while letter is not None:
    letter = next(letters, None)
    if letter:
        print(letter)

Cuidado con los bucles infinitos de while en Python

En particular, los bucles while en Python son buenos implementando bucles infinitos. Esto puede parecer absurdo en un principio. No obstante, los bucles infinitos producidos por un error son muy temidos, ya que la condición nunca deja de ser cierta, lo que provoca que el programa se cuelgue:

while True:
    print("Forever…")

De hecho, los bucles infinitos intencionados tienen un gran número de usos. Un bucle infinito while producido por error suele ser provocado por una expresión que siempre se evalúa como True. He aquí un ejemplo:

while 1 == 1 + 1 - 1:
    print("And ever…")
Consejo

Si te encuentras atrapado en un bucle while infinito de REPL en Python, la combinación de teclas Ctrl + C o Strg + C te sacará del apuro. Esta combinación envía una señal de parada al intérprete de Python, que cancela la ejecución del bucle.

Abortar y evitar que se ejecute un bucle while en Python

Generalmente, un bucle while sigue iterando hasta que la condición del bucle deja de cumplirse. Un truco común es utilizar una variable “Flag” como condición. Para ello, se define una variable booleana fuera del bucle y se evalúa en la condición del bucle. Cuando se alcanza una determinada condición dentro del cuerpo del bucle se activa el Flag. Cuando la condición se evalúe previamente a la siguiente ejecución, el nuevo valor hará que el bucle finalice:

aborted = False
while not aborted:
    print("Still going…")
    if some_cond:
        # will prevent next iteration
        aborted = True

Este patrón se encuentra muy a menudo, pero no es especialmente elegante. ¿Qué pasa si hay más código en el cuerpo del bucle después de que se haya activado la variable Flag? Este debería ser omitido. De forma práctica, Python conoce la sentencia break para los bucles while.

Si se ejecuta una sentencia break dentro de un bucle, este finaliza inmediatamente. Así pues, la sentencia break de los bucles es similar a la sentencia return de las funciones. Sin embargo, break no devuelve un valor. Es habitual utilizar la sentencia break para poner fin a un bucle infinito:

while True:
    print("Still going…")
    if some_cond:
        break
        # we never get here
        print("We shouldn't be here")
# we end up here after breaking
print("Done.")

La sentencia continue se asemeja conceptualmente a la sentencia break. Si se ejecuta una sentencia continue en el cuerpo del bucle, se omite el código que le sigue y comienza una nueva iteración. Con break y continue se pueden implementar menús sencillos basados en texto, como era habitual en los primeros juegos de ordenador:

# `continue` takes us here
while True:
    print("Press G to start a new game")
    print("Press S to see stats")
    print("Press M for main menu")
    print("Press Q to quit")
    
    key_press = input("Your choice \n")[0].upper()
    print(f"You pressed {key_press}")
    
    if key_press == "G":
        # start game routines
        print("Starting game …")
    elif key_press == "S":
        # show stats
        print("Showing stats …")
    elif key_press == "M":
        # back to main menu
        print("Returning to menu")
        continue
    elif key_press == "Q":
        # break out of loop
        print("Quitting")
        break
    else:
        print("Unknown command. Try again")
# `break` takes us here
...

Salir de bucles while de Python anidados entre sí

El uso de bucles anidados puede generar rápidamente confusión. Es por eso por lo que es útil interrumpir el último bucle iniciado por medio de una sentencia break. ¿Pero qué pasa si también se pretende abandonar un bucle superior de la cadena en la misma línea? No hay una palabra clave para este caso concreto; en principio, existe una solución con una variable flag.

Una forma más elegante de salir de los bucles while anidados en Python es combinar break, continue y else con un poco de habilidad. Se construye una cadena de bucles de manera que el bucle exterior termine cuando se ejecute una sentencia break en el bucle interior. Para ello, se utiliza una sentencia continue dentro del bloque else interno para omitir el break externo si es necesario:

# `continue` takes us here
while outer_cond:
    while inner_cond:
        ...
        if some_cond:
            print("Breaking out of inner loop")
            break
    # no inner `break` occured
    else:
        print("Continuing outer loop")
        # skip rest of outer loop body
        continue
    # we only get here if inner `break` occured
    print("Breaking out of outer loop")
    break
# outer `break` takes us here
...

¿Cómo utilizar el bucle while en Python?

En la práctica, el bucle while en Python tiene una gran cantidad de usos diferentes. Generalmente, los bucles while se utilizan para algoritmos en los que el número de repeticiones no está fijado de antemano o cambia durante su ejecución. Los bucles while se utilizan a menudo en combinación con otras estructuras de control, como las bifurcaciones y las sentencias try-else. He aquí algunos ejemplos.

Consumir una colección en Python con el bucle while

Python ofrece el bucle for para iterar sobre los elementos de una colección. Al menos es así mientras no se cambie la colección desde el cuerpo interno del bucle. ¿Pero qué pasa si se realizan cambios a medida que se itera por los elementos? Es posible imaginar que se quieren eliminar elementos de la colección mientras se itera. En este caso, se dice que la colección se “consume”.

Los bucles for pueden causar errores extraños si la colección subyacente cambia durante la iteración. El bucle while es el más adecuado para consumir una colección en Python. Se utiliza la colección directamente como una condición del bucle while. Mientras la colección contenga elementos, se evaluará como verdadera en el contexto booleano. Si la colección se encuentra vacía, el bucle finaliza:

pieces = ['x', 'o', 'o', 'o', 'x', 'o', 'x', 'x']
while pieces:
    piece = pieces.pop()
    print(f"Removed {piece}")
# test
assert pieces == []

Implementar la propia función range() con el bucle while en Python

Los bucles while en Python pueden utilizarse para implementar los llamados generators. Un generator es una función que utiliza la sentencia yield y genera valores bajo demanda. Se escribe la implementación propia de la función range(). Se utiliza la sentencia yield dentro de un bucle while para generar números continuos. Cuando el código llega a la sentencia yield, esta emite un valor y el bucle se detiene:

def my_range(start, stop):
    # only positive ranges implemented
    if stop <= start:
        return None
    current = start
    while current < stop:
        yield current
        # next call of next() continues here
        current += 1
# test
assert list(my_range(7, 9)) == list(range(7, 9))

Optimizar un modelo con el bucle while de Python

La optimización de modelos forma parte del repertorio habitual de las disciplinas científicas. Un modelo se calcula a partir de un conjunto de parámetros. A continuación, se ajustan los parámetros y se vuelve a calcular el modelo. Se utiliza una función objetivo para estimar si al modificarse los parámetros se obtiene un mejor modelo. Si este fuera el caso, se repite el proceso. De este modo, se pueden encontrar los parámetros óptimos para el modelo de forma iterativa.

Normalmente, el modelo se ajusta tras unas cuantas iteraciones, de modo que cada vez se consigue una mayor precisión. Si el progreso cae por debajo de un determinado umbral, se detiene la optimización. Para garantizar que el bucle termine, también se limita el número máximo de ejecuciones permitidas. He aquí un enfoque esquemático de la optimización del modelo utilizando un bucle while de Python:

limit = 5
round = 0
progress = True
while progress and round < limit:
    # attempt next optimization
    round += 1
    # compute optimized parameters
    params = optimize(params)
    # make a copy of the old model
    old_model = model
    # compute new model using optimized parameters
    model = run(model, params)
    # worthwhile to further optimize?
    progress = has_improved(model, old_model)

Implementación de la conexión en Python con el bucle while y la estructura try except

El establecimiento de la conexión puede fallar. Por lo tanto, es deseable intentar establecer la conexión en varios intentos. Como no es posible saber de antemano cuántos intentos serán necesarios, se recurre a un bucle while en Python. Además, se limita el número máximo de intentos. En el caso de que ninguno de los intentos tenga éxito, se aborta con un mensaje de error.

He aquí una solución esquemática: se utiliza una sentencia try except para captar el error al establecer la conexión. El uso de una sentencia break en el bloque try y una sentencia continue en el bloque except asegura que se itere de manera correcta. Si la conexión falla, se vuelve a intentar con continue. Si se establece la conexión, se termina el bucle mediante break:

max_tries = 10
attempt = 0
conn = None
# `continue` takes us here
while attempt < max_tries:
    attempt += 1
    print("Trying to get a connection")
    try:
        # might raise `ConnectionException`
        conn = get_connection()
        # got our connection
        break
    # `get_connection()` raised `ConnectionException`
    except ConnectionException:
        print("Something went wrong. Trying again")
        continue
# went through `max_tries` unsuccessful connection attempts
else:
    assert conn is None
    print("Unable to connect")
# `break` takes us here
assert conn is not None
print("Connection established")

Iterar sobre estructuras recursivas con el bucle while de Python

El bucle while en Python es perfecto para resolver problemas recursivos. A modo de ejemplo, el bucle es adecuado para iterar sobre:

  • listas anidadas
  • estructuras de árboles
  • gráficos

Descubre cómo funciona mediante el ejemplo de una matrioska. El conocido juguete consiste en muñecas dentro de muñecas. Desde el exterior no se puede ver cuántas muñecas hay, de modo que se procede de forma iterativa: se abre la muñeca exterior para ver lo que hay dentro. Si lo que se encuentra es otra matrioska, se repite el proceso. Un caso típico para emplear un bucle while.

Se diseña la matrioska como una lista anidada con un solo elemento en cada nivel. Se incluye otra lista o un objeto que no es una lista. Se itera sobre la matrioska siempre que se trate de una lista. Dentro del cuerpo del bucle se utiliza una asignación para pasar a un nivel inferior. En algún momento se encuentra un elemento que no sea una lista. Cuando llegue ese momento, se detiene la iteración:

def open_matroshka(matroshka):
    """
    * Matroshka dolls stacked five levels deep, with `None` inside:
    `matroshka = [[[[[None]]]]]`
    """
    while type(matroshka) is list:
        print("Opening the next matroshka")
        # go one level deeper
        matroshka = matroshka.pop()
    else:
        print(f"Reached the bottom and found {matroshka}")
        return matroshka
# test
matroshka = [[[[[None]]]]]
assert open_matroshka(matroshka) is None

Este sencillo enfoque funciona independientemente de la profundidad de los niveles anidados. El algoritmo desciende hasta el menor nivel y saca el elemento objetivo: la magia del bucle while de Python en acción.

Ejecutar un bucle while de Python en un tablero de juego

Un escenario de uso común de los bucles while en Python es el de mover una pieza por un tablero. Si quieres tener la garantía de que vas a recorrer todas las casillas, necesitas dos bucles anidados. Para ello, hay que tener en cuenta el tiempo de ejecución, ya que, si dispones de mucho tiempo, el programa puede aprovechar y funcionar durante todo este.

Se implementa un simple “Random Walk”, en el que una figura del tablero se mueve aleatoriamente hasta llegar a un destino. Este patrón de movimiento puede encontrarse, por ejemplo, en el movimiento de una partícula en un líquido o en el movimiento de una mosca volando por el espacio. Como no se sabe de antemano cuántas iteraciones se van a necesitar, se recurre a un bucle while de Python.

Primero se define la función random_walk() que contiene el bucle while. Con el operador Python se comprueba si la posición actual es igual a la de destino. Si no, se sigue iterando:

def random_walk(board = (10, 10), goal = (4, 4), start = (0, 0)):
    # ensure arguments are valid
    if not (goal[0] in range(board[0]) and goal[1] in range(board[1]) and start[0] in range(board[0]) and start[1] in range(board[1])):
        print(f"Goal {goal} and / or start position {start} outside of board with dimensions {board}")
        return None, 0
    steps = 0
    pos = start
    # as long as we haven't reached the goal
    while not pos == goal:
        # move to neighboring position
        pos = get_neighbor(pos, board)
        steps += 1
        print(f"Moved to position {pos}")
    print(f"Reached goal at {pos} after {steps} steps")
    return pos, steps

Además, se define una función auxiliar get_neighbor() que retorna un posible campo alrededor de una determinada posición:

def get_neighbor(pos, bounds):
    from random import choice
    """
        x = 0    . . .    m
            - - - - - - - -
     y = 0 |
           |
         . |           (x, y-1)
         . |  (x-1, y) (x, y)  (x+1, y)
         . |           (x, y+1)
           |
         n |
   
    """
    x, y = pos
    # computer neighbors
    neighbors = [ (x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1) ]
    # filter out neighbors that are outside of bounds
    neighbors = [ pos for pos in neighbors if 0 <= pos[0] < bounds[0] and 0 <= pos[1] < bounds[1] ]
    # select a random neighbor
    neighbor = choice(neighbors)
    return neighbor

A continuación, se pone a prueba nuestra implementación del random walk:

random_walk(board = (10, 10), goal = (4, 4), start = (5, 7))