Saltar a contenido

Bucles

Big wheel (1)

  1. Gary Lopater Unsplash

Cuando queremos hacer algo más de una vez, necesitamos recurrir a un bucle. En esta sección veremos las distintas sentencias en Python que nos permiten repetir un bloque de código.

La sentencia while

El primer mecanismo que existe en Python para repetir instrucciones es usar la sentencia while. La semántica tras esta sentencia es: «Mientras se cumpla una condición1 haz algo».

Veamos un primer ejemplo con un sencillo bucle que repite un saludo mientras así se desee:

>>> want_greet = 'S'#(1)!

>>> while want_greet == 'S':#(2)!
...     print('Hola qué tal!')
...     want_greet = input('¿Quiere otro saludo? [S/N] ')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] N
Que tenga un buen día

  1. Importante dar un valor inicial.
    • La condición del bucle se comprueba en cada nueva iteración (repetición).
    • En este caso chequeamos que la variable want_greet sea igual a 'S'.

Iteración

En este contexto, llamamos iteración a cada «repetición» del bucle. Iterar significa «repetir» una determinada acción.

Romper un bucle while

Python ofrece la posibilidad de romper o finalizar un bucle antes de que se cumpla la condición de parada.

Supongamos que en el ejemplo anterior establecemos un máximo de 4 saludos:

>>> MAX_GREETS = 4

>>> num_greets = 0
>>> want_greet = 'S'

>>> while want_greet == 'S':
...     print('Hola qué tal!')
...     num_greets += 1
...     if num_greets == MAX_GREETS:
...         print('Número máximo de saludos alcanzado')
... ┌────── break
...    want_greet = input('¿Quiere otro saludo? [S/N] ')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
Número máximo de saludos alcanzado
Que tenga un buen día

Como hemos visto en este ejemplo, break nos permite finalizar el bucle una vez que hemos llegado al máximo número de saludos. Pero si no hubiéramos llegado a dicho límite, el bucle habría seguido hasta que el usuario indicara que no quiere más saludos.

Solución alternativa

Otra forma de resolver este ejercicio sería incorporar la (segunda) condición al bucle:

while want_greet == 'S' and num_questions < MAX_GREETS:
    ...

Comprobar la rotura

Python nos ofrece la posibilidad de detectar si el bucle ha acabado de forma ordinaria, esto es, ha finalizado por no cumplirse la condición establecida.

Para ello podemos hacer uso de la sentencia else como parte del propio bucle. Si el bucle while finaliza normalmente (sin llamada a break) el flujo de control pasa a la sentencia opcional else.

Veamos su comportamiento siguiendo con el ejemplo que venimos trabajando:

>>> MAX_GREETS = 4

>>> num_greets = 0
>>> want_greet = 'S'

>>> while want_greet == 'S':
...     print('Hola qué tal!')
...     num_greets += 1
...     if num_greets == MAX_GREETS:
...         print('Máximo número de saludos alcanzado')
...         break
...     want_greet = input('¿Quiere otro saludo? [S/N] ')
... else:#(1)!
...     print('Usted no quiere más saludos')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] N
Usted no quiere más saludos
Que tenga un buen día

  1. El flujo de control entrará por aquí cuando want_greet sea distinto de 'S' y por tanto no se cumpla la condición del bucle.

Contexto

La sentencia else sólo tiene sentido en aquellos bucles que contienen un break.

Continuar un bucle while

Hay situaciones en las que, en vez de romper un bucle, nos interesa saltar adelante hacia la siguiente iteración.

Para ello Python nos ofrece la sentencia continue que hace precisamente eso, descartar el resto del código del bucle y saltar a la siguiente iteración.

Continuamos con el ejemplo anterior pero ahora vamos a contar el número de respuestas válidas:

>>> want_greet = 'S'
>>> valid_options = 0

>>> while want_greet == 'S':
...    print('Hola qué tal!')
...    want_greet = input('¿Quiere otro saludo? [S/N] ')
...    if want_greet not in 'SN':#(1)!
...        print('No le he entendido pero le saludo')
...        want_greet = 'S'#(2)!
... └─────  continue#(3)!
...     valid_options += 1
... print(f'{valid_options} respuestas válidas')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] A
No le he entendido pero le saludo
Hola qué tal!
¿Quiere otro saludo? [S/N] B
No le he entendido pero le saludo
Hola qué tal!
¿Quiere otro saludo? [S/N] N
2 respuestas válidas
Que tenga un buen día

  1. Comprobamos si la entrada es un «sí» o un «no».
  2. Asignamos «sí» a la opción para que el bucle pueda seguir funcionando.
  3. Saltamos de nuevo al comienzo del bucle.

Bucle infinito

Si no establecemos correctamente la condición de parada o bien el valor de alguna variable está fuera de control, es posible que lleguemos a una situación de bucle infinito, del que nunca podamos salir.

Veamos un ejemplo de esto:

>>> num = 1

>>> while num != 10:
...     num += 2
...
^CTraceback (most recent call last):
  Cell In[4], line 1
    while num != 10:
KeyboardInterrupt

El problema que surje es que la variable num toma los valores 1, 3, 5, 7, 9, 11, ... por lo que nunca se cumple la condición de parada del bucle. Esto hace que repitamos «eternamente» la instrucción de incremento.

Parar el bucle

Para abortar una situación de bucle infinito podemos pulsar en el teclado la combinación Ctrl+C. Se puede ver reflejado en el intérprete de Python por KeyboardInterrupt.

Una posible solución a este problema sería reescribir la condición de parada en el bucle:

>>> num = 1

>>> while num < 10:
...     num += 2
...

Bucles infinitos como recurso

Hay ocaciones en las que un supuesto bucle «infinito» puede ayudarnos a resolver un problema.

Si retomamos el ejemplo de los saludos, podríamos reescribirlo utilizando un «bucle infinito» de la siguiente manera:

>>> while True:#(1)!
...     print('Hola qué tal!')
...     if (want_greet := input('¿Quiere otro saludo? [S/N] ')) != 'S':#(2)!
...         break#(3)!
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] N
Que tenga un buen día

  1. Este bucle por sí solo implicaría un bucle infinito, pero veremos que no es así.
  2. Usando el operador morsa pedimos la entrada y comprobamos su valor.
  3. La sentencia break nos «salva» de este bucle infinito cuando no se quieren más saludos.

En comparación con el enfoque «clásico» del bucle while:

  • Como ventaja podemos observar que no es necesario asignar un valor inicial a want_greet antes de entrar al bucle.
  • Como desventaja el código resulta menos «idiomático» ya que la condición del bucle no nos da ninguna pista de lo que está ocurriendo.

Ejercicio

pypas   m5-limited

La sentencia for

Python permite recorrer aquellos tipos de datos que sean iterables, es decir, que admitan iterar2 sobre ellos. Algunos ejemplos de tipos y estructuras de datos iterables (que permiten ser iteradas/recorridas) son: cadenas de texto, listas, tuplas, diccionarios, ficheros, etc.

La sentencia for nos permite realizar esta acción.

A continuación se plantea un ejemplo en el que recorremos (iteramos sobre) una cadena de texto:

>>> word = 'Python'

>>> for letter in word:#(1)!
...     print(letter)
...
P
y
t
h
o
n

    • La variable letter va tomando cada uno de los elementos de word.
    • Dado que word es una cadena de texto, cada elemento es un caracter.

Variable de asignación

La variable de asignación que utilizamos en el bucle for para ir tomando los valores puede tener cualquier nombre. Al fin y al cabo es una variable que definimos según nuestras necesidades.

Suele ser de buen estilo que sea un nombre en singular relacionado con la estructura que recorre:

  • for item in items:
  • for num in numbers:
  • for product in products:
  • for line in lines:

Romper un bucle for

Una sentencia break dentro de un for rompe el bucle, igual que veíamos para los bucles while.

Veamos un ejemplo recorriendo una cadena de texto y parando el bucle cuando encontremos una letra t minúscula:

>>> word = 'Python'

>>> for letter in word:
...     if letter == 't':
...         break
...     print(letter)
...
P
y

Comprobación de rotura y continuación

Tanto la comprobación de rotura de un bucle como la continuación a la siguiente iteración se llevan a cabo del mismo modo que hemos visto con los bucles de tipo while.

Ejercicio

pypas   count-vowels

Secuencias de números

Es muy habitual hacer uso de secuencias de números en bucles. Python no tiene una instrucción específica para ello. Lo que sí aporta es una función range() que devuelve un flujo de números en el rango especificado3.

La técnica para la generación de secuencias de números es muy similar a la utilizada en los «slices» de cadenas de texto. En este caso disponemos de la función range(start, stop, step):

Parámetro Carácter Por defecto
start Opcional 0
stop Obligatorio -
step Opcional 1

Veamos distintos casos de uso:

>>> for i in range(3):#(1)!
...     print(i)
...
0
1
2

  1. start=0, stop=3, step=1
    • También se podría haber escrito range(0, 3) pero es innecesario.
    • Recordar que al ser índice 0, el rango llega hasta 1 menos que el límite superior.

>>> for i in range(1, 6, 2):#(1)!
...     print(i)
...
1
3
5

  1. start=1, stop=6, step=2

>>> for i in range(2, -1, -1):#(1)!
...     print(i)
...
2
1
0

    • start=2, stop=-1, step=-1
    • Vamos «hacia atrás» por tanto el límite final estará uno por debajo de donde queremos llegar.

i, j, k

Se suelen utilizar nombres de variables i, j, k para lo que se denominan contadores4. Este tipo de variables toman valores numéricos enteros como en los ejemplos anteriores.

No conviene generalizar el uso de estas variables a situaciones en las que, claramente, tenemos la posibilidad de asignar un nombre semánticamente más significativo.

Ejercicio

pypas   prime

Usando el guión bajo

Hay situaciones en las que no necesitamos usar la variable que toma valores en el rango, sino que únicamente queremos repetir una acción un número determinado de veces.

Para estos casos se suele recomendar usar el guión bajo _ como nombre de variable, que da a entender que no estamos usando esta variable de forma explícita:

>>> for _ in range(10):
...     print('Repeat me 10 times!')
...
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!

Ejercicio

pypas   pow

Bucles anidados

Como ya vimos en las sentencias condicionales, el anidamiento es una técnica por la que incluimos distintos niveles de encapsulamiento de sentencias, unas dentro de otras, con mayor nivel de profundidad.

En el caso de los bucles también es posible hacer anidamiento, tanto para bucles while como para bucles for.

Matrioskas (1)

Veamos un ejemplo de 2 bucles anidados en el que generamos todas las tablas de multiplicar:

>>> for num_table in range(1, 10):#(1)!
...     for mul_factor in range(1, 10):#(2)!
...         result = num_table * mul_factor
...         print(f'{num_table} * {mul_factor} = {result}')
...     print('----------')
...
1 * 1 = 1
1 * 2 = 2
1 * 3 = 3
1 * 4 = 4
1 * 5 = 5
1 * 6 = 6
1 * 7 = 7
1 * 8 = 8
1 * 9 = 9
----------
2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
2 * 4 = 8
2 * 5 = 10
2 * 6 = 12
2 * 7 = 14
2 * 8 = 16
2 * 9 = 18
----------
3 * 1 = 3
3 * 2 = 6
3 * 3 = 9
3 * 4 = 12
3 * 5 = 15
3 * 6 = 18
3 * 7 = 21
3 * 8 = 24
3 * 9 = 27
----------
4 * 1 = 4
4 * 2 = 8
4 * 3 = 12
4 * 4 = 16
4 * 5 = 20
4 * 6 = 24
4 * 7 = 28
4 * 8 = 32
4 * 9 = 36
----------
5 * 1 = 5
5 * 2 = 10
5 * 3 = 15
5 * 4 = 20
5 * 5 = 25
5 * 6 = 30
5 * 7 = 35
5 * 8 = 40
5 * 9 = 45
----------
6 * 1 = 6
6 * 2 = 12
6 * 3 = 18
6 * 4 = 24
6 * 5 = 30
6 * 6 = 36
6 * 7 = 42
6 * 8 = 48
6 * 9 = 54
----------
7 * 1 = 7
7 * 2 = 14
7 * 3 = 21
7 * 4 = 28
7 * 5 = 35
7 * 6 = 42
7 * 7 = 49
7 * 8 = 56
7 * 9 = 63
----------
8 * 1 = 8
8 * 2 = 16
8 * 3 = 24
8 * 4 = 32
8 * 5 = 40
8 * 6 = 48
8 * 7 = 56
8 * 8 = 64
8 * 9 = 72
----------
9 * 1 = 9
9 * 2 = 18
9 * 3 = 27
9 * 4 = 36
9 * 5 = 45
9 * 6 = 54
9 * 7 = 63
9 * 8 = 72
9 * 9 = 81
----------

  1. Para cada valor que toma la variable num_table la otra variable mul_factor toma todos sus valores.
  2. Como resultado tenemos una combinación completa de los valores en el rango especificado.
Complejidad ciclomática

Podemos añadir todos los niveles de anidamiento que queramos. Eso sí, hay que tener en cuenta que cada nuevo nivel de anidamiento supone un importante aumento de la complejidad ciclomática de nuestro código, lo que se traduce en mayores tiempos de ejecución.

Ejercicio

pypas   mosaic

Ejercicios

  1. pypas   letdig
  2. pypas   m3-sum-limited
  3. pypas   repeat-please
  4. pypas   one-tree
  5. pypas   chess-horse
  6. pypas   domino
  7. pypas   fmin
  8. pypas   ascii-table
  9. pypas   guess-number
  10. pypas   gcd
  11. pypas   hamming
  12. pypas   cartesian
  13. pypas   cumprod-sq
  14. pypas   isalpha
  15. pypas   kpower
  16. pypas   fibonacci

  1. Esta condición del bucle se conoce como condición de parada

  2. Realizar cierta acción varias veces. En este caso la acción es tomar cada elemento. 

  3. Una de las grandes ventajas es que la «lista» generada no se construye explícitamente, sino que cada valor se genera bajo demanda. Esta técnica mejora el consumo de recursos, especialmente en términos de memoria. 

  4. Esto viene de tiempos antiguos en FORTRAN donde i era la primera letra que tenía valor entero por defecto.