Generadores de Python

    驴Qu茅 es un generador?

    Una python generador es una funci贸n que produce una secuencia de resultados. Funciona manteniendo su estado local, de modo que la funci贸n pueda reanudarse de nuevo exactamente donde se detuvo cuando se llame en ocasiones posteriores. Por lo tanto, puede pensar en un generador como algo as铆 como un poderoso iterador.

    El estado de la funci贸n se mantiene mediante el uso de la palabra clave yield, que tiene la siguiente sintaxis:

    yield [expression_list]
    

    Esta palabra clave de Python funciona de manera muy similar a usar return, pero tiene algunas diferencias importantes, que explicaremos a lo largo de este art铆culo.

    Los generadores se introdujeron en PEP 255, junto con el yield declaraci贸n. Est谩n disponibles desde la versi贸n 2.2 de Python.

    驴C贸mo funcionan los generadores de Python?

    Para comprender c贸mo funcionan los generadores, usemos el siguiente ejemplo simple:

    # generator_example_1.py
    
    def numberGenerator(n):
         number = 0
         while number < n:
             yield number
             number += 1
    
    myGenerator = numberGenerator(3)
    
    print(next(myGenerator))
    print(next(myGenerator))
    print(next(myGenerator))
    

    El c贸digo anterior define un generador llamado numberGenerator, que recibe un valor n como argumento, y luego lo define y lo usa como valor l铆mite en un ciclo while. Adem谩s, define una variable llamada number y le asigna el valor cero.

    Llamar al generador “instanciado” (myGenerator) con el next() El m茅todo ejecuta el c贸digo del generador hasta el primer yield declaraci贸n, que devuelve 1 en este caso.

    Incluso despu茅s de devolvernos un valor, la funci贸n mantiene el valor de la variable number para la pr贸xima vez se llama a la funci贸n y aumenta su valor en uno. Entonces, la pr贸xima vez que se llame a esta funci贸n, continuar谩 justo donde se qued贸.

    Llamar a la funci贸n dos veces m谩s, nos proporciona los siguientes 2 n煤meros en la secuencia, como se ve a continuaci贸n:

    $ python generator_example_1.py
    0
    1
    2
    

    Si tuvi茅ramos que llamar a este generador de nuevo, habr铆amos recibido un StopIteration excepci贸n ya que se hab铆a completado y regresado de su ciclo interno while.

    Esta funcionalidad es 煤til porque podemos usar generadores para crear iterables din谩micamente sobre la marcha. Si tuvi茅ramos que envolver myGenerator con list(), luego obtendr铆amos una matriz de n煤meros (como [0, 1, 2]) en lugar de un objeto generador, con el que es un poco m谩s f谩cil trabajar en algunas aplicaciones.

    La diferencia entre rendimiento y rendimiento

    La palabra clave return devuelve un valor de una funci贸n, momento en el que la funci贸n pierde su estado local. Por lo tanto, la pr贸xima vez que llamemos a esa funci贸n, comenzar谩 de nuevo desde su primera declaraci贸n.

    Por otra parte, yield mantiene el estado entre llamadas a funciones, y se reanuda desde donde lo dej贸 cuando llamamos al next() m茅todo de nuevo. As铆 que si yield se llama en el generador, luego, la pr贸xima vez que se llame al mismo generador, lo recuperaremos despu茅s de la 煤ltima yield declaraci贸n.

    Usando retorno en un generador

    Un generador puede usar un return declaraci贸n, pero solo sin un valor de retorno, que tiene la forma:

    return
    

    Cuando el generador encuentra el return declaraci贸n, procede como en cualquier otra funci贸n return.

    Como dice el PEP 255:

    Tenga en cuenta que return significa “He terminado y no tengo nada interesante que devolver”, tanto para las funciones generadoras como para las funciones no generadoras.

    Modifiquemos nuestro ejemplo anterior agregando una cl谩usula if-else, que discriminar谩 los n煤meros superiores a 20. El c贸digo es el siguiente:

    # generator_example_2.py
    
    def numberGenerator(n):
      if n < 20:
         number = 0
         while number < n:
             yield number
             number += 1
      else:
         return
    
    print(list(numberGenerator(30)))
    

    En este ejemplo, dado que nuestro generador no producir谩 ning煤n valor, ser谩 una matriz vac铆a, ya que el n煤mero 30 es mayor que 20. Por lo tanto, la instrucci贸n return funciona de manera similar a una instrucci贸n break en este caso.

    Esto se puede ver a continuaci贸n:

    $ python generator_example_2.py
    []
    

    Si hubi茅ramos asignado un valor menor que 20, los resultados habr铆an sido similares al primer ejemplo.

    Usando next () para iterar a trav茅s de un generador

    Podemos analizar los valores producidos por un generador usando el next() m茅todo, como se ve en el primer ejemplo. Este m茅todo le dice al generador que solo devuelva el siguiente valor del iterable, pero nada m谩s.

    Por ejemplo, el siguiente c贸digo imprimir谩 en pantalla los valores de 0 a 9.

    # generator_example_3.py
    
    def numberGenerator(n):
         number = 0
         while number < n:
             yield number
             number += 1
    
    g = numberGenerator(10)
    
    counter = 0
    
    while counter < 10:
        print(next(g))
        counter += 1
    

    El c贸digo de arriba es similar a los anteriores, pero llama a cada valor producido por el generador con la funci贸n next(). Para hacer esto, primero debemos instanciar un generador g, que es como una variable que mantiene nuestro estado generador.

    Cuando la funci贸n next() se llama con el generador como argumento, la funci贸n del generador de Python se ejecuta hasta que encuentra un yield declaraci贸n. Luego, el valor obtenido se devuelve a la persona que llama y el estado del generador se guarda para su uso posterior.

    Ejecutar el c贸digo anterior producir谩 el siguiente resultado:

    $ python generator_example_3.py
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    

    Nota: Sin embargo, existe una diferencia de sintaxis entre Python 2 y 3. El c贸digo anterior usa la versi贸n Python 3. En Python 2, el next() puede usar la sintaxis anterior o la siguiente sintaxis:

    print(g.next())
    

    驴Qu茅 es una expresi贸n generadora?

    Las expresiones generadoras son como lista de comprensiones, pero devuelven un generador en lugar de una lista. Fueron propuestos en PEP 289 y pasaron a formar parte de Python desde la versi贸n 2.4.

    La sintaxis es similar a las listas por comprensi贸n, pero en lugar de corchetes, usan par茅ntesis.

    Por ejemplo, nuestro c贸digo de antes podr铆a modificarse usando expresiones generadoras de la siguiente manera:

    # generator_example_4.py
    
    g = (x for x in range(10))
    print(list(g))
    

    Los resultados ser谩n los mismos que en nuestros primeros ejemplos:

    $ python generator_example_4.py
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    Las expresiones generadoras son 煤tiles cuando se utilizan funciones de reducci贸n como sum(), min()o max(), ya que reducen el c贸digo a una sola l铆nea. Tambi茅n son mucho m谩s cortos de escribir que una funci贸n de generador de Python completa. Por ejemplo, el siguiente c贸digo sumar谩 los primeros 10 n煤meros:

    # generator_example_5.py
    
    g = (x for x in range(10))
    print(sum(g))
    

    Despu茅s de ejecutar este c贸digo, el resultado ser谩:

    $ python generator_example_5.py
    45
    

    Gesti贸n de excepciones

    Una cosa importante a tener en cuenta es que el yield la palabra clave no est谩 permitida en el try parte de una construcci贸n de prueba / finalmente. Por lo tanto, los generadores deben asignar recursos con precauci贸n.

    Sin embargo, yield puede aparecer en finally cl谩usulas, except cl谩usulas, o en el try parte de las cl谩usulas try / except.

    Por ejemplo, podr铆amos haber creado el siguiente c贸digo:

    # generator_example_6.py
    
    def numberGenerator(n):
      try:
         number = 0
         while number < n:
             yield number
             number += 1
      finally:
         yield n
    
    print(list(numberGenerator(10)))
    

    En el c贸digo anterior, como resultado de la finally cl谩usula, el n煤mero 10 se incluye en la salida y el resultado es una lista de n煤meros del 0 al 10. Esto normalmente no suceder铆a ya que la declaraci贸n condicional es number < n. Esto se puede ver en el resultado a continuaci贸n:

    $ python generator_example_6.py
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    

    Env铆o de valores a generadores

    Los generadores tienen una herramienta poderosa en el send() m茅todo para generadores-iteradores. Este m茅todo se defini贸 en PEP 342 y est谩 disponible desde la versi贸n 2.5 de Python.

    los send() El m茅todo reanuda el generador y env铆a un valor que se utilizar谩 para continuar con el siguiente yield. El m茅todo devuelve el nuevo valor proporcionado por el generador.

    La sintaxis es send() o send(value). Sin ning煤n valor, el m茅todo de env铆o es equivalente a un next() llamada. Este m茅todo tambi茅n puede utilizar None como valor. En ambos casos, el resultado ser谩 que el generador avance su ejecuci贸n al primer yield expresi贸n.

    Si el generador sale sin producir un nuevo valor (como al usar return), la send() m茅todo aumenta StopIteration.

    El siguiente ejemplo ilustra el uso de send(). En la primera y tercera l铆nea de nuestro generador, le pedimos al programa que asigne la variable number el valor anteriormente cedido. En la primera l铆nea despu茅s de nuestra funci贸n de generador, instanciamos el generador y generamos una primera yield en la siguiente l铆nea llamando al next funci贸n. As铆, en la 煤ltima l铆nea enviamos el valor 5, que ser谩 utilizado como entrada por el generador, y considerado como su rendimiento anterior.

    # generator_example_7.py
    
    def numberGenerator(n):
         number = yield
         while number < n:
             number = yield number 
             number += 1
    
    g = numberGenerator(10)    # Create our generator
    next(g)                    # 
    print(g.send(5))
    

    Nota: Porque no hay ning煤n valor obtenido cuando se crea el generador por primera vez, antes de usar send(), debemos asegurarnos de que el generador arroj贸 un valor usando next() o send(None). En el ejemplo anterior, ejecutamos el next(g) line por esta raz贸n, de lo contrario, obtendr铆amos un error que diga “TypeError: no se puede enviar un valor que no sea Ninguno a un generador reci茅n iniciado”.

    Despu茅s de ejecutar el programa, imprime en pantalla el valor 5, que es lo que le enviamos:

    $ python generator_example_7.py
    5
    

    La tercera l铆nea de nuestro generador de arriba tambi茅n muestra una nueva caracter铆stica de Python introducida en el mismo PEP: expresiones de rendimiento. Esta caracter铆stica permite yield cl谩usula que se utilizar谩 en el lado derecho de una declaraci贸n de asignaci贸n. El valor de una expresi贸n de rendimiento es None, hasta que el programa llame al m茅todo send(value).

    Conexi贸n de generadores

    Desde Python 3.3, una nueva caracter铆stica permite a los generadores conectarse y delegar en un subgenerador.

    La nueva expresi贸n se define en PEP 380 y su sintaxis es:

    yield from <expression>
    

    d贸nde <expression> es una expresi贸n que eval煤a a un iterable, que define el generador de delegaci贸n.

    Veamos esto con un ejemplo:

    # generator_example_8.py
    
    def myGenerator1(n):
        for i in range(n):
            yield i
    
    def myGenerator2(n, m):
        for j in range(n, m):
            yield j
    
    def myGenerator3(n, m):
        yield from myGenerator1(n)
        yield from myGenerator2(n, m)
        yield from myGenerator2(m, m+5)
    
    print(list(myGenerator1(5)))
    print(list(myGenerator2(5, 10)))
    print(list(myGenerator3(0, 10)))
    

    El c贸digo anterior define tres generadores diferentes. El primero, llamado myGenerator1, tiene un par谩metro de entrada, que se utiliza para especificar el l铆mite en un rango. El segundo, llamado myGenerator2, es similar al anterior, pero contiene dos par谩metros de entrada, que especifican los dos l铆mites permitidos en el rango de n煤meros. Despu茅s de este, myGenerator3 llamadas myGenerator1 y myGenerator2 para ceder sus valores.

    Las 煤ltimas tres l铆neas de c贸digo imprimen en pantalla tres listas generadas a partir de cada uno de los tres generadores previamente definidos. Como podemos ver cuando ejecutamos el programa a continuaci贸n, el resultado es que myGenerator3 utiliza los rendimientos obtenidos de myGenerator1 y myGenerator2, para generar una lista que combine las tres listas anteriores.

    El ejemplo tambi茅n muestra una aplicaci贸n importante de los generadores: la capacidad de dividir una tarea larga en varias partes separadas, lo que puede ser 煤til cuando se trabaja con grandes conjuntos de datos.

    $ python generator_example_8.py
    [0, 1, 2, 3, 4]
    [5, 6, 7, 8, 9]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
    

    Como puede ver, gracias a la yield from sintaxis, los generadores se pueden encadenar juntos para una programaci贸n m谩s din谩mica.

    Beneficios de los generadores

    • C贸digo simplificado

    Como se ve en los ejemplos que se muestran en este art铆culo, los generadores simplifican el c贸digo de una manera muy elegante. Esta simplificaci贸n y elegancia del c贸digo son a煤n m谩s evidentes en las expresiones del generador, donde una sola l铆nea de c贸digo reemplaza un bloque completo de c贸digo.

    • Mejor presentaci贸n

    Los generadores trabajan en la generaci贸n perezosa (bajo demanda) de valores. Esto da como resultado dos ventajas. Primero, menor consumo de memoria. Sin embargo, este ahorro de memoria funcionar谩 en nuestro beneficio si usamos el generador solo una vez. Si usamos los valores varias veces, puede que valga la pena generarlos de una vez y guardarlos para su uso posterior.

    La naturaleza bajo demanda de los generadores tambi茅n significa que es posible que no tengamos que generar valores que no se utilizar谩n y, por lo tanto, se habr铆an desperdiciado ciclos si se hubieran generado. Esto significa que su programa puede usar solo los valores necesarios sin tener que esperar hasta que se hayan generado todos.

    Cuando usar generadores

    Los generadores son una herramienta avanzada presente en Python. Hay varios casos de programaci贸n en los que los generadores pueden aumentar la eficiencia. Algunos de estos casos son:

    • Procesamiento de grandes cantidades de datos: los generadores proporcionan c谩lculos a pedido, tambi茅n llamados evaluaci贸n diferida. Esta t茅cnica se utiliza en el procesamiento de flujos.
    • Tuber铆as: los generadores apilados se pueden usar como tuber铆as, de manera similar a las tuber铆as Unix.
    • Concurrencia: los generadores se pueden utilizar para generar (simular) la concurrencia.

    Terminando

    Los generadores son un tipo de funci贸n que genera una secuencia de valores. Como tales, pueden actuar de manera similar a los iteradores. Su uso da como resultado un c贸digo m谩s elegante y un rendimiento mejorado.

    Estos aspectos son a煤n m谩s evidentes en las expresiones generadoras, donde una l铆nea de c贸digo puede resumir una secuencia de declaraciones.

    La capacidad de trabajo de los generadores se ha mejorado con nuevos m茅todos, como send()y declaraciones mejoradas, como yield from.

    Como resultado de estas propiedades, los generadores tienen muchas aplicaciones 煤tiles, como generar conductos, programaci贸n concurrente y ayudar a crear flujos a partir de grandes cantidades de datos.

    Como consecuencia de estas mejoras, Python se est谩 convirtiendo cada vez m谩s en el lenguaje preferido en la ciencia de datos.

    驴Para qu茅 ha utilizado los generadores? 隆H谩znoslo saber en los comentarios!

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *