Python Language Introducción


Ejemplo

Las expresiones de los generadores son similares a las listas, diccionarios y conjuntos de comprensión, pero están entre paréntesis. Los paréntesis no tienen que estar presentes cuando se usan como el único argumento para una llamada de función.

expression = (x**2 for x in range(10))

Este ejemplo genera los 10 primeros cuadrados perfectos, incluido 0 (en el que x = 0).

Las funciones del generador son similares a las funciones regulares, excepto que tienen una o más declaraciones de yield en su cuerpo. Dichas funciones no pueden return ningún valor (sin embargo, se permiten return vacías si desea detener el generador antes).

def function():
    for x in range(10):
        yield x**2

Esta función del generador es equivalente a la expresión del generador anterior, produce el mismo.

Nota : todas las expresiones generadoras tienen sus propias funciones equivalentes , pero no al revés.


Se puede usar una expresión generadora sin paréntesis si ambos paréntesis se repetirían de lo contrario:

sum(i for i in range(10) if i % 2 == 0)   #Output: 20
any(x = 0 for x in foo)                   #Output: True or False depending on foo
type(a > b for a in foo if a % 2 == 1)    #Output: <class 'generator'>

En lugar de:

sum((i for i in range(10) if i % 2 == 0))
any((x = 0 for x in foo))
type((a > b for a in foo if a % 2 == 1))

Pero no:

fooFunction(i for i in range(10) if i % 2 == 0,foo,bar)
return x = 0 for x in foo
barFunction(baz, a > b for a in foo if a % 2 == 1)

Al llamar a una función de generador se genera un objeto generador , que luego se puede iterar. A diferencia de otros tipos de iteradores, los objetos generadores solo se pueden atravesar una vez.

g1 = function()
print(g1)  # Out: <generator object function at 0x1012e1888>

Observe que el cuerpo de un generador no se ejecuta inmediatamente: cuando llama a function() en el ejemplo anterior, devuelve inmediatamente un objeto generador, sin ejecutar siquiera la primera declaración de impresión. Esto permite que los generadores consuman menos memoria que las funciones que devuelven una lista, y permite crear generadores que producen secuencias infinitamente largas.

Por esta razón, los generadores a menudo se utilizan en la ciencia de datos y en otros contextos que involucran grandes cantidades de datos. Otra ventaja es que otro código puede usar inmediatamente los valores generados por un generador, sin esperar a que se produzca la secuencia completa.

Sin embargo, si necesita usar los valores producidos por un generador más de una vez, y si generarlos cuesta más que almacenarlos, puede ser mejor almacenar los valores generados como una list que volver a generar la secuencia. Consulte 'Restablecer un generador' a continuación para obtener más detalles.

Normalmente, un objeto generador se usa en un bucle, o en cualquier función que requiera un iterable:

for x in g1:
    print("Received", x)

# Output:
# Received 0
# Received 1
# Received 4
# Received 9
# Received 16
# Received 25
# Received 36
# Received 49
# Received 64
# Received 81

arr1 = list(g1)
# arr1 = [], because the loop above already consumed all the values.
g2 = function()
arr2 = list(g2)  # arr2 = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Como los objetos generadores son iteradores, uno puede recorrerlos manualmente usando la función next() . Al hacerlo, se devolverán los valores cedidos uno por uno en cada invocación posterior.

Bajo el capó, cada vez que llama a next() en un generador, Python ejecuta declaraciones en el cuerpo de la función del generador hasta que llega a la siguiente declaración de yield . En este punto, devuelve el argumento del comando de yield y recuerda el punto en el que ocurrió. Llamar a next() una vez más reanudará la ejecución desde ese punto y continuará hasta la próxima declaración de yield .

Si Python llega al final de la función del generador sin encontrar más yield , se StopIteration una excepción StopIteration (esto es normal, todos los iteradores se comportan de la misma manera).

g3 = function()
a = next(g3)  # a becomes 0
b = next(g3)  # b becomes 1
c = next(g3)  # c becomes 2
...
j = next(g3)  # Raises StopIteration, j remains undefined

Tenga en cuenta que en el generador Python 2, los objetos tenían métodos .next() que se podían usar para iterar a través de los valores producidos manualmente. En Python 3, este método fue reemplazado por el estándar .__next__() para todos los iteradores.

Restablecer un generador

Recuerde que solo puede recorrer los objetos generados por un generador una vez . Si ya ha iterado a través de los objetos en una secuencia de comandos, cualquier otro intento de hacerlo dará como resultado None .

Si necesita usar los objetos generados por un generador más de una vez, puede definir la función del generador de nuevo y usarla por segunda vez, o bien, puede almacenar la salida de la función del generador en una lista en el primer uso. Volver a definir la función del generador será una buena opción si está manejando grandes volúmenes de datos, y almacenar una lista de todos los elementos de datos ocuparía mucho espacio en el disco. A la inversa, si es costoso generar los artículos inicialmente, es posible que prefiera almacenar los artículos generados en una lista para poder reutilizarlos.