Bucles en Python

Python ofrece una variedad de construcciones para hacer bucles. Este artículo los presenta y da consejos sobre su uso específico. Además, también echaremos un vistazo a...

Elegir la construcción de bucle correcta

Python ofrece una variedad de construcciones para hacer bucles. Este artículo los presenta y da consejos sobre su uso específico. Además, también veremos el rendimiento de cada construcción de bucle en su código de Python. Puede que te sorprenda.

Bucles, Bucles, Bucles

Un lenguaje de programación generalmente consta de varios tipos de elementos básicos, como asignaciones, declaraciones y bucles. La idea detrás de un bucle es repetir acciones individuales que se indican en el cuerpo del bucle. Los diferentes tipos de bucles son comunes:

  • siempre que una condición especificada sea verdadera (mientras que la condición haga algo)
  • hasta que se cumpla cierta condición (hacer algo hasta que la condición)
  • para un número fijo de pasos (iteraciones) (for/from 'x' to 'y' do sth.)
  • bucle sin fin y salida/interrupción en condición (mientras que condition1 hace algo y sale en condition2)

Construcciones de bucle compatibles con Python

Python admite una cantidad parcial de las construcciones nombradas anteriormente, además ofrece extensiones únicas para los tipos que hemos mencionado.

Ciclos while básicos {#ciclos while básicos}

1
2
while condition:
    statements

Siempre que se cumpla la "condición", todas las declaraciones en el cuerpo del bucle while se ejecutan al menos una vez. Cada vez que se ejecutan las declaraciones, la condición se vuelve a evaluar. Escribir un bucle se ve así:

Listado 1

1
2
3
4
5
6
fruits = ["banana", "apple", "orange", "kiwi"]
position = 0
while position < len(fruits):
    print(fruits[position])
    position = position + 1
print("reached end of list")

Este código generará un elemento de la lista después del siguiente:

1
2
3
4
5
banana
apple
orange
kiwi
reached end of list

Ciclos while con la cláusula else

Esta construcción es específica del lenguaje Python, pero bastante útil:

1
2
3
4
while condition:
    statements
else:
    statements

Este ciclo while actúa de manera similar al ciclo regular while como se presentó anteriormente. Las declaraciones en la parte else se ejecutan tan pronto como la condición deja de ser cierta. Por ejemplo, en caso de que se llegue al final de una lista, como en nuestro ejemplo anterior. Puede interpretarlo como entonces si la condición del bucle ya no se cumple.

Listado 2

1
2
3
4
5
6
7
fruits = ["banana", "apple", "orange", "kiwi"]
position = 0
while position < len(fruits):
    print(fruits[position])
    position = position + 1
else:
    print("reached end of list")

Esto generará un elemento de la lista después del siguiente, más el texto adicional de la instrucción imprimir en la cláusula else:

1
2
3
4
5
banana
apple
orange
kiwi
reached end of list

Este tipo de bucle con una cláusula else es útil para generar mensajes o ejecutar declaraciones en caso de que su condición falle.

Una cosa importante a tener en cuenta es que la cláusula else no se ejecuta si se interrumpe del bucle while o si se produce un error dentro del bucle while.

Bucles infinitos while

Los bucles infinitos siempre se enseñan como componentes críticos y deben evitarse si la condición de ruptura es un asunto complicado. Aunque hay casos en los que los bucles infinitos te ayudan a escribir código de forma elegante.

Estos son solo algunos casos de uso de bucles infinitos:

  • dispositivos que intentan mantener activas las conexiones de red como puntos de acceso inalámbrico
  • clientes que intentan intercambiar datos constantemente con un sistema host, como un sistema de archivos basado en red (NFS o Samba/CIFS)
  • bucles de juego para dibujar y actualizar el estado de tu juego
1
2
3
4
while True:
    if condition:
        break
    statements

Tenga en cuenta que las declaraciones en el cuerpo de un ciclo sin fin se ejecutan al menos una vez. Es por eso que recomiendo escribir la condición de interrupción como la primera declaración después del encabezado del bucle. Siguiendo nuestro código de ejemplo, un bucle infinito tiene el siguiente aspecto:

Listado 3

1
2
3
4
5
6
7
8
fruits = ["banana", "apple", "orange", "kiwi"]
position = 0
while True:
    if position >= len(fruits):
        break
    print(fruits[position])
    position = position + 1
print("reached end of list")

Bucles for con un iterador

Trabajar con listas se describe como usar la palabra clave for en combinación con un iterador. El pseudocódigo queda de la siguiente manera:

1
2
for temp_var in sequence:
    statements

Esto simplifica el código de Python para procesar nuestra lista de la siguiente manera:

Listado 4

1
2
3
4
fruits = ["banana", "apple", "orange", "kiwi"]
for food in fruits:
    print(food)
print("reached end of list")

En este tipo de construcción de bucle, el intérprete de Python maneja la iteración sobre la lista y se encarga de que el bucle no se ejecute fuera del rango de la lista. Tenga en cuenta que las declaraciones en el cuerpo del bucle se ejecutan una vez para cada elemento de la lista, sin importar si es solo uno o veinte mil.

En caso de que la lista esté vacía, las declaraciones en el cuerpo del bucle no se ejecutan. Cambiar la lista en términos de agregar o eliminar elementos dentro del ciclo for puede confundir al intérprete de Python y causar problemas, así que tenga cuidado.

Ciclos for con iterador y cláusula else

Similar al bucle while, Python también ofrece una declaración else para el bucle for. Funciona de manera similar y puede interpretarse como entonces, igual que antes. El pseudocódigo queda de la siguiente manera:

1
2
3
4
for temp_var in sequence:
    statements
else:
    statements

Usando esta palabra clave, nuestro código cambia de la siguiente manera:

Listado 5

1
2
3
4
5
fruits = ["banana", "apple", "orange", "kiwi"]
for food in fruits:
    print(food)
else:
    print("reached end of list")

Construcciones de bucle no admitidas {#construcciones de bucle no admitidas}

Como se indicó al principio, hay muchos estilos de bucle diferentes. Sin embargo, Python no los admite todos. Python no admite un bucle do-until o un bucle foreach, como posiblemente se sepa de PHP. Tales casos se resuelven usando el operador in de Python que crea un código bastante sexy si te familiarizas con él. Vea las formas alternativas de escribir un bucle desde arriba.

¿Qué bucle elegir?

En general, los bucles while condition requieren que se especifique una condición antes de las declaraciones del bucle. Esto puede conducir al caso de que las declaraciones en el cuerpo del bucle nunca se ejecuten. Además, no siempre está claro cuántas veces se ejecutará el bucle para los bucles while. En su lugar, los bucles for se centran en el iterador que especifica con qué frecuencia se ejecutan las sentencias en el cuerpo del bucle.

Se recomienda utilizar un bucle for si sabe exactamente el número de elementos que se van a iterar. Por el contrario, un bucle while es mejor cuando tienes una expresión booleana para evaluar, y no una lista de elementos para recorrer.

Mejorar la calidad de su código {#mejorar la calidad de su código}

Muchos programadores jóvenes no siempre se preocupan por la calidad de su código, en gran parte porque han crecido en una época en la que nadie tiene que pensar en la memoria y la potencia de la CPU; simplemente tenemos mucho disponible en las computadoras modernas. En cambio, los desarrolladores más experimentados (también conocidos como "mayores") son más propensos a optimizar su código tanto como sea posible y pueden recordar contar las instrucciones de la CPU y la cantidad de celdas de memoria que están en uso.

Entonces, ¿qué significa calidad hoy en día? En términos de efectividad, cubre escribir la menor cantidad de código posible y ejecutar código de manera efectiva, solo tantas instrucciones de procesador como sea necesario. En primer lugar, con los intérpretes, los tiempos de ejecución y los marcos actuales, es bastante difícil calcularlo correctamente y, en segundo lugar, siempre hay una compensación entre estas dos medidas. Las preguntas clave son, con qué frecuencia se usará este código y cuánto tiempo dedicaremos a optimizarlo para ganar unos microsegundos de tiempo de CPU.

Como ejemplo, veremos un bucle for que itera sobre una lista. Por lo general, lo escribimos de la siguiente manera:

Listado 6

1
2
for entry in range(0, 3):
    print(entry)

Esto genera los valores 0, 1 y 2. El método range() crea el iterable [0, 1, 2] cada vez que se evalúa la cabeza del ciclo. Por lo tanto, es mejor escribirlo de la siguiente manera:

Listado 7

1
2
3
entryRange = range(0, 3)
for entry in entryRange:
    print(entry)

Si bien esto puede no parecer una gran optimización para el ejemplo dado, considere si el rango fue de 0 a 1,000,000 o más. A medida que crece nuestra lista, ahorramos más tiempo y nuestro código se ejecuta más rápido.

Además, estas sentencias se pueden expresar como un bucle while:

Listado 8

1
2
3
4
5
entryRange = range(0, 3)
index = 0
while index < len(entryRange):
    print(entryRange[index])
    index = index + 1

Y en este punto parece un poco inútil incluso usar la función range(). En su lugar, también podríamos usar una constante para el condicional y el índice como contador para el condicional y la impresión:

1
2
3
4
index = 0
while index < 3:
    print(index)
    index = index + 1

Pequeñas optimizaciones como estas pueden proporcionar pequeñas mejoras de rendimiento para sus bucles, especialmente cuando la cantidad de iteraciones se vuelve muy grande.

Pruebas de rendimiento {#pruebas de rendimiento}

Hasta ahora hablamos sobre el código de bucle y cómo escribirlo correctamente. Una prueba de rendimiento puede ayudar a arrojar algo de luz. La idea está amablemente tomada de un interesante artículo de blog de Ned Batchelder [1].

En uso está la herramienta perf que realiza pruebas de rendimiento para el código del programa que se ejecuta [2]. La llamada básica es perf stat program mientras que stat abrevia estadísticas y program es la llamada que nos gustaría evaluar. Para probar nuestras variantes de bucle, se realizaron estas llamadas:

Listado 9

1
2
3
4
5
6
7
8
perf stat python3 while-1.py
perf stat python3 while-2.py
perf stat python3 while-3.py
perf stat python3 for-4.py
perf stat python3 for-5.py
perf stat python3 for-6.py
perf stat python3 for-7.py
perf stat python3 while-8.py

Estos resultados son el promedio basado en 10 ejecuciones debido a las diferencias de carga en el kernel de Linux. La siguiente tabla muestra los resultados:

Tema Listado 1 Listado 2 Listado 3 Listado 4 Listado 5


reloj de tareas (ms) 20,160077 18,535264 15,975387 15,427334 15,503672 cambios de contexto 10 11 10 13 10 migraciones de CPU 0 0 2 1 1 fallos de pagina 851 849 855 848 851 ciclos 41.915.010 44.938.837 44.403.696 42.983.392 42.489.206 instrucciones 46.833.820 46.803.187 46.926.383 46.596.667 46.701.350

Para los Listados 6-8 se ve de la siguiente manera:

Tema Listado 6 Listado 7 Listado 8


reloj de tareas (ms) 16,480322 18,193437 15,734627 cambios de contexto 9 11 11 migraciones de CPU 0 0 1 fallos de página 850 851 853 ciclos 42.424.639 42.569.550 43.038.837 instrucciones 46.703.893 46.724.190 46.695.710

Conclusión

Python ofrece diferentes formas de repetir acciones y escribir bucles de escritura. Hay variantes por caso de uso específico. Nuestras pruebas han demostrado que los bucles están en la misma dimensión con pequeñas diferencias, y la optimización del intérprete de Python es bastante buena.

Enlaces y referencias

Agradecimientos

El autor desea agradecer a Geroldo Rupprecht y Mandy Neumeyer por su apoyo y comentarios durante la preparación de este artículo.

Licensed under CC BY-NC-SA 4.0