Cómo y por qué debería usar los generadores de Python

Los generadores han sido una parte importante de Python desde que se introdujeron con PEP 255.

Las funciones generadoras le permiten declarar una función que se comporta como un iterador.

Permiten a los programadores hacer un iterador de una manera rápida, fácil y limpia.

¿Qué es un iterador, puede preguntar?

Un iterador es un objeto sobre el que se puede iterar (enlazar). Se utiliza para abstraer un contenedor de datos para que se comporte como un objeto iterable. Probablemente ya use algunos objetos iterables todos los días: cadenas, listas y diccionarios, por nombrar algunos.

Un iterador está definido por una clase que implementa el Protocolo de iterador . Este protocolo busca dos métodos dentro de la clase: __iter__y __next__.

Vaya, retroceda. ¿Por qué querrías hacer iteradores?

Ahorro de espacio en la memoria

Los iteradores no calculan el valor de cada elemento cuando se crean instancias. Solo lo calculan cuando lo solicitas. Esto se conoce como evaluación perezosa.

La evaluación diferida es útil cuando tiene un conjunto de datos muy grande para calcular. Le permite comenzar a usar los datos inmediatamente, mientras se calcula todo el conjunto de datos.

Digamos que queremos obtener todos los números primos que son más pequeños que un número máximo.

Primero definimos la función que verifica si un número es primo:

def check_prime(number): for divisor in range(2, int(number ** 0.5) + 1): if number % divisor == 0: return False return True

Luego, definimos la clase de iterador que incluirá los métodos __iter__y __next__:

class Primes: def __init__(self, max): self.max = max self.number = 1
 def __iter__(self): return self
 def __next__(self): self.number += 1 if self.number >= self.max: raise StopIteration elif check_prime(self.number): return self.number else: return self.__next__()

Primesse instancia con un valor máximo. Si el próximo primo es mayor o igual que max, el iterador generará una StopIterationexcepción, que finaliza el iterador.

Cuando solicitamos el siguiente elemento en el iterador, se incrementará numberen 1 y verificará si es un número primo. Si no es así, volverá a llamar __next__hasta que numberesté en prime. Una vez que lo está, el iterador devuelve el número.

Al usar un iterador, no estamos creando una lista de números primos en nuestra memoria. En cambio, estamos generando el siguiente número primo cada vez que lo solicitamos.

Probémoslo:

primes = Primes(100000000000)
print(primes)
for x in primes: print(x)
---------
235711...

Cada iteración del Primesobjeto llama __next__a generar el siguiente número primo.

Los iteradores solo se pueden iterar una vez. Si intenta iterar primesnuevamente, no se devolverá ningún valor. Se comportará como una lista vacía.

Ahora que sabemos qué son los iteradores y cómo hacer uno, pasaremos a los generadores.

Generadores

Recuerde que las funciones generadoras nos permiten crear iteradores de una manera más simple.

Los generadores introducen la yielddeclaración en Python. Funciona un poco returnporque devuelve un valor.

La diferencia es que guarda el estado de la función. La próxima vez que se llame a la función, la ejecución continúa desde donde se quedó , con los mismos valores de variable que tenía antes de ceder.

Si transformamos nuestro Primesiterador en un generador, se verá así:

def Primes(max): number = 1 while number < max: number += 1 if check_prime(number): yield number
primes = Primes(100000000000)
print(primes)
for x in primes: print(x)
---------
235711...

¡Eso es bastante pitónico! ¿Podemos hacerlo mejor?

¡Si! Podemos usar Generator Expressions , introducido con PEP 289.

Esta es la lista de comprensión equivalente de generadores. Funciona exactamente de la misma manera que una lista de comprensión, pero la expresión está rodeada de ()en lugar de [].

La siguiente expresión puede reemplazar nuestra función generadora anterior:

primes = (i for i in range(2, 100000000000) if check_prime(i))
print(primes)
for x in primes: print(x)
---------

    
     235711...
    

Ésta es la belleza de los generadores en Python.

En resumen…

  • Los generadores le permiten crear iteradores de una manera muy pitónica.
  • Los iteradores permiten la evaluación diferida, generando solo el siguiente elemento de un objeto iterable cuando se solicita. Esto es útil para conjuntos de datos muy grandes.
  • Los iteradores y generadores solo se pueden iterar una vez.
  • Las funciones de generador son mejores que los iteradores.
  • Las expresiones generadoras son mejores que los iteradores (solo para casos simples).

También puede consultar mi explicación de cómo usé Python para encontrar personas interesantes a las que seguir en Medium.

Para obtener más actualizaciones, sígueme en Twitter.