English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Tutoriales Básicos de Python

Control de Flujo de Python

Funciones en Python

Tipos de datos en Python

Operaciones de Archivo de Python

Objetos y Clases de Python

Fecha y Hora de Python

Conocimientos Avanzados de Python

Manual de Referencia de Python

Generadores en Python

En este artículo, aprenderá cómo crear iteraciones sencillas con generadores en Python, cómo se diferencian de los iteradores y las funciones normales, y por qué se debe usar.

¿Qué es un generador en Python?

Con PythonConstruirIteradorHay muchos costos; debemos implementar una clase con métodos __iter__() y __next__() para rastrear el estado interno, lanzar StopIteration cuando no hay valores para devolver, etc.

Esto es tanto largo como intuitivo. Los generadores pueden ser útiles en este caso.

Los generadores de Python son una manera sencilla de crear iteradores. Todas las sobrecargas que mencionamos anteriormente son manejadas automáticamente por los generadores de Python.

En resumen, un generador es una función que devuelve un objeto (iterador) sobre el que podemos iterar (uno valor a la vez).

¿Cómo crear generadores en Python?

Crear generadores en Python es muy simple. Tan fácil como definir una función común usando una sentencia yield en lugar de una sentencia return.

Si una función contiene al menos una sentencia yield (puede contener otras sentencias yield o return), se convierte en una función generadora. yield y return ambos devuelven algunos valores desde la función.

La diferencia radica en que cuando la sentencia return completa termina una función, la sentencia yield detiene la función, guarda su estado completo y luego continúa ejecutándose en llamadas posteriores.

La diferencia entre la función generadora y la función normal

Esta es la diferencia entre la función generadora yLas funciones normales deLas diferencias.

  • Las funciones generadoras contienen una o más sentencias yield.

  • Al llamar, devuelve un objeto (iterador), pero no comienza a ejecutarse inmediatamente.

  • Métodos como __iter__() y __next__() se implementan automáticamente. Por lo tanto, podemos usar next() para recorrer los elementos.

  • Una vez que la función ha producido un resultado, la función se detiene y el control se transfiere al llamador.

  • Las variables locales y su estado se recuerdan entre llamadas consecutivas.

  • Finalmente, cuando la función termina, se lanza automáticamente StopIteration en llamadas posteriores.

Este es un ejemplo que ilustra todos los puntos mencionados anteriormente. Tenemos una función generadora my_gen() nombrada con varias sentencias yield.

# Una función generadora simple
def my_gen():
    n = 1
    print('Esto es la primera impresión')
    # La función generadora contiene una instrucción yield
    yield n
    n += 1
    print('Esto es la segunda impresión')
    yield n
    n += 1
    print('Esto es la última impresión')
    yield n

La ejecución interactiva en el intérprete se muestra a continuación. Ejecute estos comandos en el Shell de Python para ver la salida.

>>> # Devuelve un objeto, pero no comienza a ejecutarse inmediatamente.
>>> a = my_gen()
>>> # Podemos usar next() para recorrer estos elementos.
>>> next(a)
Esto es la primera impresión
1
>>> # Una vez que la función ha producido un resultado, la función se detiene y el control se transfiere al llamador.
>>> # Las variables locales y su estado se recuerdan entre llamadas consecutivas.
>>> next(a)
Esto es la segunda impresión
2
>>> next(a)
Esto es la última impresión
3
>>> # Finalmente, cuando el función termina, se desencadena automáticamente StopIteration al llamar nuevamente.
>>> next(a)
Traceback (llamada más reciente última):
...
StopIteration
>>> next(a)
Traceback (llamada más reciente última):
...
StopIteration

Una cosa interesante que hay que notar en el ejemplo anterior es que entre cada llamada se recuerda la variablenvalores.

Diferente de las funciones normales, las variables locales no se destruyen cuando se crea la función. Además, un objeto generador solo se puede iterar una vez.

Para reiniciar el proceso, necesitamos crear otro objeto generador utilizando algo como = my_gen().

Nota:Lo último que hay que tener en cuenta es que podemos usar generadores directamente conBucle forjuntos.

Esto se debe a que el bucle for acepta un iterador y lo itera utilizando la función next(). Cuando se desencadena StopIteration, termina automáticamente.Entender cómo implementar bucles for en Python.

# Una función generadora simple
def my_gen():
    n = 1
    print('Esto es la primera impresión')
    # La función generadora contiene una instrucción yield
    yield n
    n += 1
    print('Esto es la segunda impresión')
    yield n
    n += 1
    print('Esto es la última impresión')
    yield n
# Uso del bucle for
for item in my_gen():
    print(item)

Al ejecutar el programa, la salida es:

Esto es la primera impresión
1
Esto es la segunda impresión
2
Esto es la última impresión
3

Generadores de Python con bucle

El ejemplo anterior no es muy útil, solo lo estudiamos para entender lo que ocurre en el fondo.

Por lo general, las funciones generadoras se implementan mediante bucles con condiciones de terminación apropiadas.

Vamos a tomar como ejemplo un generador para invertir una cadena.

def rev_str(my_str):
    length = len(my_str)
    for i in range(length - 1,-1,-1):
        yield my_str[i]
# El bucle for para invertir una cadena
# Salida:
# o
# l
# l
# e
# h
for char in rev_str("hello"):
     print(char)

En este ejemplo, utilizamos la función range() para obtener índices en orden inverso en un bucle for.

Resulta que esta función generadora no solo es aplicable a las cadenas, sino también a otros tipos de objetos iterables, comolista,tuplaetc.

Expresiones generadoras en Python

Con la expresión generadora se puede crear fácilmente generadores simples de manera dinámica. Esto facilita la construcción de generadores.

funciones lambda creadasanónima igual quecreando una función generadora anónima.

La sintaxis de la expresión generadora es similar aPythonenComprensiónSintaxis. Pero reemplazar los corchetes con paréntesis.

La principal diferencia entre la comprensión de lista y la expresión generadora es que, aunque la comprensión de lista genera toda la lista, la expresión generadora genera un elemento a la vez.

Son un poco perezosos, solo generan elementos cuando son necesarios. Por esta razón, las expresiones generadoras son mucho más eficientes en términos de uso de memoria que la comprensión de lista equivalente.

# Inicialización de la lista
my_list = [1, 3, 6, 10]
# Usar comprensión de lista para cuadrar cada elemento
# Salida: [1, 9, 36, 100]
[x**2 for x in my_list]
# Se puede hacer lo mismo con una expresión generadora
# Salida: <generator object <genexpr> en 0x0000000002EBDAF8>
(x**2 for x in my_list)

Como podemos ver, la expresión generadora no produce inmediatamente el resultado necesario. En su lugar, devuelve un objeto generador que produce elementos según demanda.

# Inicialización de la lista
my_list = [1, 3, 6, 10]
a = (x**2 for x in my_list)
# Salida: 1
print(next(a))
# Salida: 9
print(next(a))
# Salida: 36
print(next(a))
# Salida: 100
print(next(a))
# Salida: StopIteration
next(a)

Las expresiones generadoras pueden utilizarse dentro de una función. Al usarlas de esta manera, se pueden eliminar los paréntesis.

>>> sum(x**2 for x in my_list)
146
>>> max(x**2 for x in my_list)
100

¿Por qué usar generadores en Python?

Hay varios motivos por los que los generadores son una implementación atractiva.

1fácil de implementar

En comparación con los elementos correspondientes de su clase iteradora, los generadores pueden implementarse de manera clara y concisa. A continuación se muestra un ejemplo de implementación utilizando la clase iterator2un ejemplo de secuencia de potencias.

class PowTwo:
    def __init__(self, max = 0):
        self.max = max
    def __iter__(self):
        self.n = 0
        devolver self
    def __next__(self):
        si self.n > self.max:
            levantar StopIteration
        result = 2 ** self.n
        self.n += 1
        return result

Este código es muy largo. Ahora, ejecuta la misma operación con una función generadora.

def PowTwoGen(max = 0):
    n = 0
    while n < max:
        yield 2 ** n
        n += 1

Debido a que los generadores rastrean automáticamente los detalles, son más concisos y fáciles de implementar.

2Ahorro de memoria

Una función común que devuelve una secuencia crea toda la secuencia en memoria antes de devolver el resultado. Si el número de elementos en la secuencia es grande, puede afectar la eficiencia.

La implementación del generador de esta secuencia es amigable con la memoria, por lo que es la opción preferida, ya que solo puede generar un solo elemento a la vez.

3Representación de flujo infinito

Los generadores son una excelente manera de representar flujos de datos infinitos. Los flujos infinitos no se pueden almacenar en memoria, y dado que los generadores generan un solo elemento a la vez, pueden representar flujos de datos infinitos.

El siguiente ejemplo puede generar todos los números pares (al menos en teoría).

def all_even():
    n = 0
    while True:
        yield n
        n += 2

4Generador de pipeline

Los generadores se pueden usar para pipelinear una serie de operaciones. Mejor con un ejemplo para ilustrarlo.

Supongamos que tenemos un archivo de registro de una famosa cadena de restaurantes de comida rápida. El archivo de registro tiene una columna (la4Columna),esta columna rastrea la cantidad de pizzas vendidas cada hora, y queremos sumarla para obtener5Número total de pizzas vendidas en el año.

Supongamos que todo el contenido es una cadena y no hay números disponibles marcados como "N / La implementación de generadores puede ser así.

with open('sells.log') as file:
    pizza_col = (line[3] for line in file)
    per_hour = (int(x) for x in pizza_col if x != 'N/A')
    print("Total de pizzas vendidas = ", sum(per_hour))

Esta línea de producción es eficiente y fácil de leer (sí, ¡muy genial!).