Python Yield, Generadores y Expresiones de Generador

2020-07-25 09:18:01 | #programming #python | Parte 6 de 7

Después de pasar por los requisitos previos, debe estar familiarizado con iteradores como list, tuples, dicts y sets. Un generador es otro tipo de iterador, pero uno con un comportamiento diferente que realmente lo hace destacar de otros iteradores.

Para entrar en más detalles, los iteradores utilizan next para obtener el siguiente valor de una secuencia. Los generadores utilizan yield para producir una secuencia de valores. Para comprender mejor los generadores, es útil revisar algunos principios fundamentales con respecto a las funciones.

Cada vez que se llama a una función en Python, el programa ejecutará, línea por línea, todo lo que esté dentro del alcance de esa función hasta que se alcance una declaración o excepción return. En Python, cada función devolverá lo que ha indicado explícitamente o None, en ese punto, cualquier variable local dentro de la función se limpiará. A diferencia de los iterables, los generadores generan valores a medida que avanzan y no almacenan todos los valores en la memoria.

Con los generadores, no hay una lista para devolver. Los generadores simplemente recorren cada iteración y devuelven cada valor, uno por uno. Los generadores generalmente se ejecutan más rápido que las iteraciones regulares con el mismo resultado final. También ahorra mucho procesamiento porque no tiene que incurrir en ningún cálculo más allá de lo que especificó en su llamada al generador.

Python Yield y Next

Para definir una función generadora, debe utilizar yield. Siempre que ceda exista en algún lugar dentro de su función, al llamarlo, se devolverá un generador.

def generador():
    yield


gen = generador()
print(gen)

yield es una palabra clave, al igual que return, sin embargo, yield devolverá un generador en el punto en que se declaró y continuará con la iteración donde lo dejó durante el siguiente paso. Entonces, mientras que una declaración return entrega permanentemente el control al llamador de la función al final, yield lo hace, temporalmente, como va. El beneficio de esto ya no es tener que hacer un seguimiento del estado entre las llamadas o tener que devolver grandes valores en memoria al final de una llamada de función. Devuelve valores en cada paso de la iteración o llamando a next() en el generador. Esto se ilustra mejor con algunos ejemplos:

Python Iterables

Antes de mostrar algunos ejemplos de generadores, debemos recordar cómo producir los mismos valores con iterables:

Inténtalo Tú Mismo


 
  

Código de Ejemplo de Python Generator

Inténtalo Tú Mismo


 
  

Dar más control de los valores devueltos a la persona que llama

En el ejemplo anterior, solo imprimimos cada valor que se devuelve con el generador, pero, por supuesto, puede pasar estos valores a otras funciones. También tiene más flexibilidad sobre los tipos de valores cedidos. Entonces, en lugar de definir explícitamente todos los tipos de retorno en múltiples funciones, los generadores le permiten adoptar un enfoque más elegante, al mismo nivel de yield. Veamos esto, en acción, comparando iterables y generadores nuevamente.

En el siguiente ejemplo, los iterables requieren una función separada para cada tipo:

Inténtalo Tú Mismo


 
  

Una Solución Más Elegante Usando Generadores

Inténtalo Tú Mismo


 
  

Envío de Valores con Generadores Python

Al llamar a send() en un generador, puede pasar valores a medida que itera:

Inténtalo Tú Mismo


 
  

Terminando Generadores de Python

También puede terminar los generadores llamando a close() o lanzando excepciones con throw().

# Cerrar
def generador():
    try:
        yield
    except GeneratorExit:
        print("Terminando")


gen = generador()
next(gen)
gen.close()


# Lanzar excepción
def generador():
    try:
        yield
    except RuntimeError:
        yield 'value'


gen = generador()
next(gen)
val = gen.throw(RuntimeError, "Algo salió mal")

Encadenamiento y Delegación con Python 3

Los generadores también pueden encadenar operaciones utilizando el yield de <iterable>, sintaxis. Piense en esto como una abreviatura de for i in iterable: yield i:.

def generador(n):
    yield from range(n)
    yield from range(n)


print(list(generador(3)))

yield from también tiene otro beneficio adicional para los bucles al permitir que los subgeneradores reciban valores enviados y arrojen excepciones directamente desde el alcance de la llamada, y devuelvan un valor final al generador externo . El siguiente ejemplo revisa algo de lo que aprendimos sobre send.

# Generador 1
def mostrador():
    mst = 0
    while True:
        next = yield
        if next is None:
            return mst
        mst += next


# Generador 2
def almacenar_totales(totals):
    while True:
        mst = yield from mostrador()
        totals.append(mst)


totales = []  # la lista pasaremos al generador
total = almacenar_totales(totales)
next(total)  # preparate para ceder

for i in range(5):
    total.send(i)  # enviar los valores para sumar
total.send(None)  # y asegúrese de detener el generador

for i in range(3):
    total.send(i)  # comenzar de nuevo
total.send(None)  # y terminar el segundo recuento

print(totales)

Como aprendió en las secciones anteriores, todo lo que se necesita para convertir una función en un generador es la palabra clave yield. Entonces, tenemos dos generadores en este ejemplo, con el segundo generador delegando al primero.

Expresiones de Generador

Las expresiones generadoras, por otro lado, son más simples y pueden devolver un objeto generador sin yield y sin una función generadora.

Inténtalo Tú Mismo


 
  

Ejercicios de programación en Python

Resuelva los siguientes problemas, utilizando todo lo que ha aprendido hasta este punto. Siéntase libre de compartir mejores soluciones en los comentarios. Optimice cada solución, tanto como sea posible.

  1. Escribe un generador de Python que imprima todos los números enteros impares entre 1 y 10

    Entrada: Ninguna

    Rendimiento esperado: 1, 3, 5, 7, 9

    Inténtalo Tú Mismo


     
              
  2. Escriba un generador de Python que devuelva 10 enteros aleatorios entre 1 y 100

    Entrada: Ninguna

    Rendimiento esperado: 8, 9, 23, 42, 1, 4, 5, 3, 6, 2

    Inténtalo Tú Mismo


     
              
  3. Escriba un generador de Python que devuelva 10 números enteros aleatorios entre 1 y 100, pero recorra solo 3 veces

    Entrada: Ninguna

    Rendimiento esperado: 9, 33, 2

    Inténtalo Tú Mismo


     
              
  4. Escriba un generador de Python que lea filas del siguiente archivo CSV, una a la vez

    Entrada:

    id,nombre,apellido,dob
    382,John,Doe,1959/05/23
    121,Jane,Doe,1964/03/03
    329,Jack,Doe,1961/07/15
    119,James,Doe,1989/08/15
    99,Joffrey,Doe,286AC

    Rendimiento esperado: 9, 33, 2

    def obtener_registro():
        with open('input.csv', 'r', newline='') as data:
            filas = data.readlines()
            headers = [f.strip('\n\r') for f in filas[0].split(',')]
            for fila in filas[1:]:
                registro = {}
                for i, col in enumerate(fila.split(',')):
                    registro[headers[i]] = col.rstrip('\n\r')
                yield(registro)
    
    
    gen = obtener_registro()
    print(next(gen))
    print(next(gen))
    print(next(gen))
  5. Escriba un generador de Python que lea filas del siguiente archivo CSV y se detenga una vez que llegue a una fila vacía

    Entrada:

    id,nombre,apellido,dob
    382,John,Doe,1959/05/23
    121,Jane,Doe,1964/03/03
    
    329,Jack,Doe,1961/07/15
    119,James,Doe,1989/08/15
    99,Joffrey,Doe,286AC

    Rendimiento esperado:

    {'id': '382', 'nombre': 'John', 'apellido': 'Doe', 'dob': '1959/05/23'}
    {'id': '121', 'nombre': 'Jane', 'apellido': 'Doe', 'dob': '1964/03/03'}
    Traceback (most recent call last):
      File "main.py", line XX, in 
        print(next(gen))
    StopIteration

    def obtener_registro():
        with open('input.csv', 'r', newline='') as data:
            filas = data.readlines()
            headers = [f.strip('\n\r') for f in filas[0].split(',')]
            for fila in filas[1:]:
                registro = {}
                if fila == '\n':
                    break
                for i, col in enumerate(fila.split(',')):
                    registro[headers[i]] = col.rstrip('\n\r')
                yield(registro)
    
    
    gen = obtener_registro()
    print(next(gen))
    print(next(gen))
    print(next(gen))
    print(next(gen))

¿Quieres ver más ejercicios?

Ver Ejercicios

Commentarios

Debes iniciar sesión para comentar. ¿No tienes una cuenta? Registrate gratis.

Subscribe to comments for this post

Regístrese para recibir más contenido gratuito

¿Le gustaría recibir recursos gratuitos, diseñados para ayudarlo a alcanzar sus objetivos de TI? Empiece ahora y deje su dirección de correo electrónico a continuación. Prometemos no hacer spam. También puede registrarse para obtener una cuenta gratuita o seguirnos en e interactuar con la comunidad. Puede optar por no participar en cualquier momento.



Háblanos de tu Proyecto









Contacta Con Nosotras

¿Tiene un problema de TI específico que necesita solución o simplemente tiene una pregunta general de TI? Utilice el formulario de contacto para ponerse en contacto con nosotros y un profesional de TI estará con usted en un momento.

Contratanos

Ofrecemos desarrollo web, desarrollo de software empresarial, control de calidad y pruebas, google analytics, dominios y hosting, bases de datos, seguridad, consultoría de TI y otros servicios relacionados con TI.

Tutoriales de TI gratuitos

Dirígete a nuestra sección tutoriales para aprender todo sobre cómo trabajar con soluciones de TI.

Contacto