A煤n no tienes acceso a esta clase

Crea una cuenta y contin煤a viendo este curso

Curso Profesional de Python

Curso Profesional de Python

Facundo Garc铆a Martoni

Facundo Garc铆a Martoni

Generadores

14/21
Recursos

Aportes 40

Preguntas 10

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesi贸n.

Yo uso generadores para retornar un resultado de una consulta en BigQuery. Como son much铆simos datos, es ideal iterar sobre el resultado de manera lazy

Generadores

Sugar syntax de los iteradores. Los generadores son funciones que guardan un estado. Es un iterador escrito de forma m谩s simple.

def my_gen():

  """un ejemplo de generadores"""

  print('Hello world!')
  n = 0
  yield n # es exactamente lo mismo que return pero detiene la funci贸n, cuando se vuelva a llamar a la funci贸n, seguir谩 desde donde se qued贸

  print('Hello heaven!')
  n = 1
  yield n

  print('Hello hell!')
  n = 2
  yield n


a = my_gen()
print(next(a)) # Hello world!
print(next(a)) # Hello heaven!
print(next(a)) # Hello hell!
print(next(a)) StopIteration

Ahora veremos un generator expression (es como list comprehension pero mucho mejor, porque podemos manejar mucha cantidad
de informaci贸n sin tener problemas de rendimiento):

#Generator expression

my_list = [0,1,4,7,9,10]

my_second_list = [x*2 for x in my_list] #List comprehension
my_second_gen = ()x*2 for x in my_list]) #Generator expression

En el minuto 5:44 m谩s bien seria el doble, no el cuadrado.

Python internamente utiliza operaciones con generadores para aumentar el rendimiento.
Tal es el caso de m谩x , min o sum.
Ejemplo:

suma = sum([i for i in range(100)]) # Crea la lista en memoria.
suma_gen = sum(i for i in range(100)) #Itera sobre los valores.

El el primer caso, creamos una lista que no nos servir谩 y se termina guardando el espacio en memoria, en el segundo caso se itera cada valor hasta obtener el resultado sin la necesidad de guardar en memoria cada valor.

Generator Expression

Es lo mismo que un iterador pero escrito de forma m谩s elegante.

Un 鈥渓ist comprehension鈥 es una manera sencilla de implementar un ciclo el cual nos permite crear una nueva lista con elementos basados en los valores de una lista existente. Dicha lista creada almacena cada uno de estos nuevos valores.

A diferencia de un List comprehension que puede ocupar mucha memoria pues cada valor es almacenado en la variable, un Generator expression solo los recorre, sin guardarlos en una lista nueva. Permite traer un elemento a la vez cuando se recorra usando un ciclo for.

# Generator expression

my_list = [0, 1, 4, 7, 9, 10]

my_second_list = [x*2 for x in my_list] # List comprehension
my_second_gen = (x*2 for x in my_list)  # Generator expression

Como se puede ver en el ejemplo, la diferencia en la sintaxis 煤nicamente es en el uso de par茅ntesis en lugar del uso de corchetes.

Ventajas de los Generadores:

  • Es mas f谩cil de escribir que un iterador
  • Ahorra Tiempo y Memoria
  • Permite guardar secuencias infinitas

Los Generadores son iteradores con Sugar Syntax.

Yield es una palabra clave que se usa para retornar de una funci贸n sin destruir los estados de las variables locales y cuando se llama a la funci贸n, la ejecuci贸n comienza desde el 煤ltimo yield declarado. Toda funci贸n que contenga la palabra clave yield es denominada como un generador.

Buenos d铆as.

Mi script para ver la diferencia del tiempo
al ejecutarse una list comprehension y un iterator expression

from datetime import datetime

LIST = [1, 2, 4, 5, 20, 100, 500, 1000]

def list_comprehension():
    """Function run as start"""
    time_start = datetime.now()
    list_comprehension = [x*2 for x in LIST]
    time_final = datetime.now()
    time = time_final -time_start
    print(f'Time for create list comprehension: {time}')
    
    print(f'list_comprehension: {list_comprehension}')
    return time


def generator_expression():
    """Function run as start"""
    time_start = datetime.now()
    generator_expression = (x*2 for x in LIST)
    time_final = datetime.now()
    time = time_final -time_start
    
    for i in generator_expression:
        print(i)

    print(f'Time for create generator espression: {time}')
    return time


def run():
    """Function run as start"""
    x = list_comprehension()
    y = generator_expression()
    print(f'Difference: {x - y}')


if __name__ == '__main__':
    run()

  • yield, es similar a return, con la diferencia que la pr贸xima vez que se llame a la funci贸n, esta continuar谩 desde el 煤ltimo yield.
  • Un generador es similar a un iterador.

lista de numeros impares VS generador de numeros impares

import time

impares = [2*i+1 for i in range(100)] # Crea la lista en memoria de numeros impares
impares_gen = (2*i+1 for i in range(100)) #Itera sobre los numeros impares 


print(impares)
print(type(impares))


print(impares_gen)
print(type(impares_gen))

for i in impares_gen:
    print(i)
    time.sleep(1)

si ejecutamos el siguiente c贸digo:

if __name__ == "__main__":
    my_list = [1, 2, 3, 4]
    new_list = [x for x in my_list]
    new_gene = (x for x in my_list)

    print(next(new_gene))
    print(next(new_gene))
    print(next(new_gene))
    print(next(new_gene))

obtenemos una secuencia de n煤meros

# 1
# 2
# 3
# 4

Al ejecutarlo as铆 me fue mas f谩cil comprenderlo

Generadores

  • Los iteradores tienen sugar syntax 馃挅. Son llamados generadores, los cuales son funciones que guardan un estado 馃.

  • Los generadores son funciones: 馃敟

    def my_gen():
    	"""Un ejemplo de generadores"""
    	print("Hello world!")
    	n = 0
    	yield n 
    	
    	print("Hello heaven!")
    	n = 1
    	yield n
    
    	print("Hello hell!")
    	n = 2
    	yield n
    
    # Lo instanciamos
    a = my_gen()
    print(next(a)) # Hello world!
    print(next(a)) # Hello heaven!
    print(next(a)) # Hello hell!
    print(next(a)) # StopIteration
    
  • yield este keyword es an谩loga al return, pero no termina la funci贸n, le pone pausa. La siguiente ejecuci贸n, corre despu茅s de este punto 馃憖.

  • Generator expression. 馃く Se trae un elemento a la vez, y no ocupa toda la memoria 馃槸.

    my_list = [0, 1, 2, 3, 4, 5, 6]
    
    my_second_list = [x*2 for x in my_list] # List comprehension
    my_second_gen = (x*2 for x in my_list) # Generator expression
    
  • La ventaja de los generadores es que es m谩s f谩cil de escribir que un iterador, y tiene las mismas ventajas que este 煤ltimo. 馃挄

Que alegria poder conocer este tema, simplifica mucho la sintaxis que la de un iterador

Ventajas de los Generadores:

  • Es mas f谩cil de escribir que un iterador
  • Ahorra Tiempo y Memoria
  • Permite guardar secuencias infinitas

隆Qu茅 buen tema! Claramente el saber no ocupa lugar y una vez estudiado tenemos tres obligaciones: practicar, practicar y practicar鈥 Siempre practicar.

Que cosa tan rara, nunca hab铆a visto ese yield, no se me ocurre por lo pronto una utilidad, pero seguro la debe tener, debe ser dif铆cil acceder a una funci贸n que no sabes que va a retornar

La principal diferencia entre List Comprehesion y Generator Expression adem谩s de los [ ] y ( ). Es que la List Comprehesion realiza todas las operaciones y luego de terminar muestra el resultado, mientras que el Generator Expression hace la operaci贸n solo si se le pide, y va una a una. 驴Es as铆?

Los generadores ahorran memoria y tiempo de ejecuci贸n al obtener los elementos de la estructura (iterador) solo cuando se le solicite.

#APUNTES DE LA CLASE 馃悕

Los GENERADORES son un 鈥淪ugar syntax鈥 de los iteradores. Los generadores son funciones que guardan un estado.

  • Se usa yield el cual es como un return pero pausa la funci贸n hasta donde estaba el yield. Empieza desde donde se llam贸 el 煤ltimo yield, La funci贸n guarda un estado
def my_gen():
    """ Un ejemplo de generadores"""
    print('Hello world')
    n = 0
    yield n
    
    print("hello heaven")
    n = 1
    yield n
    
    print("hello hell")
    n = 2
    yield n
    
a = my_gen()

print(next(a)) 
print(next(a))
print(next(a)) 
print(next(a)) StopIteration

Cada vez que ejecuto next(a) el c贸digo ejecuta desde el yield donde est谩. Cuando se acaban los yield usamos el StopIteration

Generator expression

Trae un elemento a la vez cu谩ndo se recorre

my_list = [0,1,2,3,4,5]

my_second_list = [x*2 for x in my_list]
my_second_gen = (x*w for x in my_list)

Fibonacci con Generators

def fibonacci(max = None):
    n1 = 0
    if max is not None and n1 > max:
        return

    yield n1

    n2 = 1

    if max is not None and n2 > max:
        return
    
    yield n2

    while True:
        output = n1 + n2

        if max is not None and output > max:
            return
        
        n1 = n2
        n2 = output

        yield output


for i in fibonacci(100):
    print(i)

Creo que nunca terminar茅 de aprender todas las palabras reservadas de python

Mas explicaciones sobre generadores pueden buscar aqu铆

https://ellibrodepython.com/yield-python

Si alguna vez te has encontrado con una funci贸n en Python que no s贸lo tiene una sentencia return, sino que adem谩s devuelve un valor haciendo uso de yield, ya has visto lo que es un generador o generator. A continuaci贸n te explicamos c贸mo se crean, para qu茅 sirven y sus ventajas. Es muy importante tambi茅n no confundir los generadores con las corrutinas, que tambi茅n usan yield pero de otra manera, sin embargo estas las dejamos para otro post.

Empecemos por lo b谩sico. Seguramente ya sepas lo que es una funci贸n y c贸mo puede devolver un valor con return.

def funcion():
return 5

Como hemos explicado, los generadores usan yield en vez de return. Vamos a ver que pasar铆a si cambiamos el return por el yield.

def generador():
yield 5

Y ahora vamos a intentar llamar a las dos 鈥渇unciones鈥.

print(funcion())
print(generador())

Salida: 5

Salida: <generator object generador at 0x103e7f0a0>

Podemos ver ya la primera diferencia al usar el yield. En el primer caso, se devuelve un 5, pero en el segundo lo que se devuelve es en realidad un objeto de la clase generator. En realidad el n煤mero 5 tambi茅n puede ser accedido en el caso del generador, pero esto lo veremos m谩s adelante.

Entonces, si una funci贸n contiene al menos una sentencia yield, se convertir谩 en una funci贸n generadora. Una funci贸n generadora se diferencia de una funci贸n normal en que tras ejecutar el yield, la funci贸n devuelve el control a qui茅n la llam贸, pero la funci贸n es pausada y el estado (valor de las variables) es guardado. Esto permite que su ejecuci贸n pueda ser reanudada m谩s adelante.
Iterando los Generadores

A continuaci贸n vamos a ver c贸mo acceder a los valores del generador. Para entenderlo mejor, te recomendamos que leas antes m谩s acerca de iterables e iteradores.

Otra de las caracter铆sticas que hacen a los generators diferentes, es que pueden ser iterados, ya que codifican los m茅todos iter() y next(), por lo que podemos usar next() sobre ellos. Dado que son iterables, lanzan tambi茅n un StopIteration cuando se ha llegado al final.

Volviendo al ejemplo anterior, vamos a ver como podemos usar el next().

a = generador()
print(next(a))

Salida: 5

Como te prometimos antes, el 5 tambi茅n se pod铆a acceder 驴has visto?. Pero vamos a ver que pasa ahora si intentamos llamar otra vez a next(). Si recuerdas, s贸lo tenemos una llamada a yield.

a = generador()
print(next(a))
print(next(a))

Salida: 5

Salida: Error! StopIteration:

Como era de esperar, tenemos una excepci贸n del tipo StopIteration, ya que el generador no devuelve m谩s valores. Esto se debe a que cada vez que usamos next() sobre el generador, se llama y se contin煤a su ejecuci贸n despu茅s del 煤ltimo yield. Y en este caso c贸mo no hay m谩s c贸digo, no se generan m谩s valores.
Creando Generadores

Vamos a ver otro ejemplo m谩s completo donde tengamos un generador que genere varios valores. En la siguiente funci贸n podemos ver como tenemos una variable n que incrementada en 1, y despu茅s retorna con yield. Lo que pasar谩 aqu铆, es que el generador generar谩 un total de tres valores.

def generador():
n = 1
yield n

n += 1
yield n

n += 1
yield n

Y haciendo uso de next() al igual que hac铆amos antes, podemos ver los valores que han sido generados. Lo que pasa por debajo, ser铆a lo siguiente:

Se entra en la funci贸n generadora, n=1 y se devuelve ese valor. Como ya hemos visto, el estado de la funci贸n se guarda (el valor de n es guardado para la siguiente llamada)
La segunda vez que usamos next() se entra otra vez en la funci贸n, pero se contin煤a su ejecuci贸n donde se dej贸 anteriormente. Se suma 1 a la n y se devuelve el nuevo valor.
La tercera llamada, realiza lo mismo.
Una cuarta llamada dar铆a un error, ya que no hay m谩s c贸digo que ejecutar.

g = generador()
print(next(g))
print(next(g))
print(next(g))

Salida: 1, 2, 3

Otra forma m谩s c贸moda de realizar lo mismo, ser铆a usando un simple bucle for, ya que el generador es iterable.

for i in generador():
print(i)

Salida: 1, 2, 3

Forma alternativa

Los generadores tambi茅n pueden ser creados de una forma mucho m谩s sencilla y en una sola l铆nea de c贸digo. Su sintaxis es similar a las list comprehension, pero cambiando el corchete [] por par茅ntesis ().

El ejemplo con list comprehensions ser铆a el siguiente. Simplemente se generan los valores de una lista elevados al cuadrado.

lista = [2, 4, 6, 8, 10]
al_cuadrado = [x**2 for x in lista]
print(al_cuadrado)

[4, 16, 36, 64, 100]

Y su equivalente con generadores ser铆a lo siguiente.

al_cuadrado_generador = (x**2 for x in lista)
print(al_cuadrado_generador)

Salida: <generator object <genexpr> at 0x103e803b8>

Y como hemos visto los valores pueden ser generados de la siguiente forma.

for i in al_cuadrado_generador:
print(i)

Salda: 4, 16, 36, 64, 100

La diferencia entre el ejemplo usando list compregensions y generators es que en el caso de los generadores, los valores no est谩n almacenados en memoria, sino que se van generando al vuelo. Esta es una de las principales ventajas de los generadores, ya que los elementos s贸lo son generados cuando se piden, lo que hace que sean mucho m谩s eficientes en lo relativo a la memoria.
Ventajas y ejemplos

Llegados a este punto tal vez te preguntes para qu茅 sirven los generadores. Lo cierto es que aunque no existieran, podr铆a realizarse lo mismo creando una clase que implemente los m茅todos iter() y next(). Veamos un ejemplo de una clase que genera los primeros 10 n煤meros (0,9) al cuadrado.

class AlCuadrado:
def init(self):
self.i = 0

def __iter__(self):
    return self

def __next__(self):
    if self.i > 9:
        raise StopIteration

    result = self.i ** 2
    self.i += 1
    return result

Y de la misma forma que us谩bamos los generadores, podemos usar nuestra clase AlCuadrado. Creamos un objeto de ella, y la iteramos. En cada iteraci贸n generar谩 un nuevo valor nuevo hasta que se llegue al final.

eleva_al_cuadrado = AlCuadrado()
for i in eleva_al_cuadrado:
print(i)
#0,1,4,9,16,25,36,49,64,81

Sin embargo esta forma es un tanto larga y tal vez confusa. Como hemos visto antes, podemos llegar a hacer lo mismo en una sola l铆nea de c贸digo. 驴Para que complicarse la vida?

Por otro lado, ya hemos mencionado que el uso de los generadores hace que no todos los valores est茅n almacenados en memoria sino que sean generados al vuelo. Vamos a ver un ejemplo donde se puede ver mejor. Supongamos que queremos sumar los primeros 100 n煤meros naturales (referencia). Una opci贸n podr铆a ser crear una lista de todos ellos y despu茅s sumarla. En este caso, todos los valores son almacenados en memoria, algo que podr铆a ser un problema si por ejemplo intentamos sumar los primeros 1e10 n煤meros.

def primerosn(n):
nums = []
for i in range(n):
nums.append(i)
return nums

print(sum(firstn(100)))

Salida: 4950

Sin embargo, podemos realizar lo mismo con un generador. En este caso los valores ser谩n generados uno por uno seg煤n se vayan necesitando.

def primerosn(n):
num = 0
for i in range(n):
yield num
num += 1
print(sum(primerosn(100)))

Salida 4950

N贸tese que es un ejemplo con fines did谩cticos, por lo que si quieres hacer esto, la mejor manera ser铆a usando un simple range() asumiendo que usas Python 3.

print(sum(range(100)))

Salida: 4950

Sugar Syntax fue lo mejor que descubr铆 este 2021.

Los generadores son az煤car sint谩ctica para los Iteradores. Son b谩sicamente funciones que guardan un estado, a diferencia de los iteradores, que son clases.

Ejemplo:

def my_gen():
	"""Un ejemplo de generadores"""
	print("Hello world")
	n = 0
	yield n

	print("Hello heaven")
	n = 1
	yield n

	print("Hello hell")
	n = 2
	yield n

a = my_gen()
print(next(a))
print(next(a))
print(next(a))
print(next(a)) StopIteration

Yield es lo mismo que return, con la diferencia de que yield, en lugar de terminar la funci贸n solo pausa la funci贸n hasta donde estaba ese yield. Es decir, que si despu茅s se vuelve a llamar a la funci贸n no va a comenzar desde el principio, sino desde donde se qued贸 el 煤ltimo yield.


Generator expressions

Son lo mismo que un list o dictionary comprehension pero para un generador.

my_secong_gen = (x*2 for x in my_list)

yield es una orden muy similar a un return, con una gran diferencia, yield pausar谩 la ejecuci贸n de tu funci贸n y guardar谩 el estado de la misma hasta que decidas usarla de nuevo.

class FiboIter():
  def __init__(self, n) -> None:
    self.n = n

  def __iter__(self):
    self.n1 = 0
    self.n2 = 1
    self.counter = 0

    return self
  
  def __next__(self):
    if self.counter == 0:
      self.counter += 1
      return self.n1
    
    elif self.counter == 1:
      self.counter += 1
      return self.n2
    
    else:
      self.aux = self.n1 + self.n2
      self.n1, self.n2 = self.n2, self.aux
      self.counter += 1

      if self.counter == self.n + 1:
        raise StopIteration
      return self.aux

Completado el reto de la clase anterior con el raise StopIteration.

Un generador que uso cuando quiero controlar timeout es time.perf_counter

Supongo que es un generador jaja porque lo instancio y luego lo llamo cada vez para contar

import time

class FiboIter():

def __init__(self, max):
    self.max = max

def __iter__(self):
    self.n1 = 0
    self.n2 = 1
    self.aux = 0
    self.counter = 0
    return self


def __next__(self):
    if not self.max or self.counter <= self.max:
    
        if self.counter == 0: 
            self.counter += 1
            return self.n1
        elif self.counter == 1:
            self.counter += 1
            return self.n2
        else: 
            self.aux = self.n1 + self.n2
            #self.n1 = self.n2
            #self.n2 = self.aux
            self.n1, self.n2 = self.n2, self.aux
            self.counter += 1 
            return self.aux
    else:
        raise StopIteration

if name == 鈥main鈥:
max = int(input('introduzca un numero m谩ximo de la serie: 鈥 ))
fibonacci = FiboIter(max)
for element in fibonacci:
print(element)
time.sleep(0.05)

Un Generador es un Iterador con Sugar Syntax, f谩cil de recordar.

Lo que m谩s me llevo de la clase es el keyword yield, es genial el contenido de la saga 馃槃

Agregu茅 otro pedazo de c贸digo para entender un poco mejor c贸mo yield recuerda el valor de la iteraci贸n anterior

def my_gem():
    'Un ejemplo de generadores'

    print('Hello World')
    n = 0
    yield n

    print('Hello Heaven')
    n = 1
    yield n

    print('Hello Hell')
    n = 2
    yield n

    print('Hello Earth')
    n = n
    yield n

a = my_gem()

print(next(a)) # Hello World
print(next(a)) # Hello Heaven
print(next(a)) # Hello Hell
print(next(a)) # Hello Earth
print(next(a)) # StopIteration

Y el Output:

Hello World
0
Hello Heaven
1
Hello Hell
2
Hello Earth
2

Qu茅 hace el keyword "Yield" ?

Chicos les comparto un enlace para complementar la clase: https://ellibrodepython.com/yield-python

Generadores:

Una forma sencilla de generar iteradores

Interesante para ver como crece el peso en memoria a medida que se agregan m谩s elementos a la List Comprehension y como no se afecta para nada una Generator Expression

Entendido a seguir practicando

Facundo menciona que los generadores son lo mismo que los iteradores, pero por algo les dice 鈥渟ugar syntax鈥 entre comillas, porque aunque tengan la misma funcionalidad, no son lo mismo
.
Claro que entre los dos, a menos que necesites algo muy complejo, los generadores son la opcion m谩s rapida y eficiente

yield = coloca en suspensi贸n la funci贸n y cuando sea llamada nuevamente ejecuta nuevamente el dato

Los generadores van sacando uno a uno los elementos

Uso de Yield

python3 generador.py
Hello world!
0
Hello heaven!
1
Hello hell!
2
Traceback (most recent call last):
  File "generador.py", line 22, in <module>
    print(next(a)) 
StopIteration