logo cosasdedevs
¿Cómo usar las funciones generadoras en Python?

¿Cómo usar las funciones generadoras en Python?



My Profile
Oct 20, 2021

Hello 👋. El pasado día 16 de octubre, el blog cumplió 2 añitos así que creo que la mejor forma que celebrarlo es con un post ¿No? 😂. Bueno, no sé si es la mejor forma, pero lo vamos a hacer así 😅. En este tutorial vamos a hablar de qué son las funciones generadoras en Python y cómo utilizarlas con ejemplos.

¿Qué son las funciones generadoras en Python?

Las funciones generadoras son funciones que son iguales a los iteradores (pincha aquí si no tienes muy claro como funcionan los iteradores), es decir, podemos iterar sobre ellas, pero la diferencia con los iteradores es que se pueden crear de forma más sencilla. Estas se valen de la sentencia yield que es muy similar a return, pero con la diferencia de que podemos usarlo varias veces en una misma función.

La primera vez que ejecutemos la función, la procesará hasta que encuentre el primer yield, la segunda vez, la función se ejecutará desde donde se quedó anteriormente, eso quiere decir que se ejecutará el código entre el primer yield y hasta el segundo y así sucesivamente hasta el último yield.

Para ver más claro como funciona la sentencia yield, vamos a verlo con un ejemplo:

def my_first_generator():

    print('Paso 1')

    yield 1

    print('Paso 2')

    yield 2

    print('Paso 3')

    yield 3

gen = my_first_generator()

print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))

En este caso, tenemos una función generadora que guardamos en una variable. La recorremos con next como si fuera un iterador. La primera vez, retornará 1, imprimirá "Paso 1" y guardará el estado. La segunda vez, se ejecutará el código a continuación del primer yield y hasta el segundo que imprimirá el mensaje "Paso 2" y retornará 2. Después hará lo mismo con la tercera parte del código y en la última iteración, lanzará la excepción StopIteration porque ya no hay más datos que recorrer.

Convertir un iterador en una función generadora

Tenemos el siguiente iterador que creamos en el tutorial sobre los iteradores:

class RangeNumber:

    def __init__(self, init=0, limit=None, increment=1):
        self.current_number = init
        self.limit = limit
        self.increment = increment

    def __iter__(self):
        return self

    def __next__(self):

        if self.limit is None or (self.limit is not None and self.current_number <= self.limit):
            current = self.current_number
            self.current_number += self.increment
            return current
        else:
            raise StopIteration


for num in RangeNumber(0, 10, 2):
    print(num)

Este iterador, nos permite obtener una lista de números en la que recibe por parámetro el número de inicio, el número de fin y cuanto queremos que se incremente en cada iteración.

Ahora bien, el código puede ser un poco complejo. Para simplificarlo, vamos a ver un ejemplo en el que vamos a convertir este iterador en una función generadora:

def range_number(init=0, limit=None, increment=1):

    current_number = init

    while True:
        if limit is None or (limit is not None and current_number <= limit):
            current = current_number
            current_number += increment
            yield current
        else:
            break

for num in range_number(0, 10, 2):
    print(num)

Como veis, lo primero que hacemos es guardar el valor de inicio en la variable current_number. Después creamos un bucle while que será igual a True. Dentro del bucle, comprobamos si cumple las condiciones, si es así, retorna el número actual e incrementa el valor de current_number. Al guardar el estado de la función, en la siguiente iteración, tendrá el valor asignado a current_number en la anterior iteración.

Para finalizar el bucle, al no cumplir las condiciones, utilizamos la sentencia break y termina la iteración de la función generadora.

¿Por qué usar iteradores o funciones generadoras?

La diferencia entre usar iteradores o funciones generadoras y la funciones normales en este tipo de casos es por el ahorro de memoria. Por ejemplo, tenemos esta función que no es un iterador o una función generadora, pero que funciona igual que en los casos anteriores (menos permitir un límite infinito):

def range_number_list(init=0, limit=100, increment=1):

    current_number = init

    my_list = []
    while True:
        if limit is not None and current_number <= limit:
            current = current_number
            current_number += increment
            yield current
            my_list.append(current)
        else:
            break

    return my_list

my_range_number_list = range_number_list(0, 10, 2)

for num in my_range_number_list:
    print(num)

En este caso, tendríamos una lista de números pequeña y no afectaría al rendimiento, pero si tuviéramos una lista enorme, sí que podría afectar, ya que primero generamos la lista con todos los números. Sin embargo, en el caso de los iteradores o con las funciones generadoras, se van generando conforme la vamos recorriendo por lo que no afecta de la misma forma en el consumo de memoria.

Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.

Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.

Nos leemos 👋.

2723 vistas

🐍 Sígueme en Twitter

Si te gusta el contenido que subo y no quieres perderte nada, sígueme en Twitter y te avisaré cada vez que cree contenido nuevo 💪
Luego ¡Te sigo!

Nos tomamos en serio tu privacidad

Utilizamos cookies propias y de terceros para recopilar y analizar datos sobre la interacción de los usuarios con cosasdedevs.com. Ver política de cookies.