Tracks
/
Python
Python
/
Temario
/
Generadores
Ge

Generadores en Python

1 ejercicio

Acerca de Generadores

Un generator es una función o expresión que devuelve un tipo especial de iterador llamado iterador generador. Los Generator-iterators son perezosos: no almacenan sus values en memoria, sino que generan sus valores cuando se necesitan.

Una función generadora se ve como cualquier otra función, pero contiene una o más expresiones yield. Cada yield suspenderá la ejecución del código, guardando el estado actual de ejecución (incluidas todas las variables locales y las sentencias try). Cuando el generador se reanuda, retoma el estado desde donde se suspendió, a diferencia de las funciones normales, que se reinician en cada llamada.

Cómo construir un generador

Los generadores se construyen de forma muy similar a otras funciones iterativas o recursivas, pero requieren una expresión yield, que exploraremos con más detalle un poco más adelante.

Un ejemplo es una función que devuelve los cuadrados de una lista de números dada.
Tal como está escrita, debe procesarse toda la entrada antes de que se pueda devolver algún valor:

>>> def squares(list_of_numbers):
...     squares = []
...     for number in list_of_numbers:
...         squares.append(number ** 2)
...     return squares

Puedes convertir esa función en un generador así:

>>> def squares_generator(list_of_numbers):
...     for number in list_of_numbers:
...         yield number ** 2

La razón de esto es que usas un generador cuando no necesitas todos los valores a la vez.

Esto ahorra memoria y capacidad de procesamiento, ya que solo se calcula el valor con el que estás trabajando en ese momento.

Cómo usar un generador

Los generadores (técnicamente, generator-iterators; consulta la nota a continuación.) son un tipo de iterator y pueden usarse en cualquier lugar de Python donde se espere un iterator o un iterable. Esto incluye funciones u objetos que requieren un iterable/iterator como argumento. Para profundizar más, consulta Cómo crear un iterador en Python.

Note

Los iteradores generadores son un subconjunto especial de iteradores. Los Iterators son el mecanismo o protocolo que permite iterar sobre iterables. Los iteradores generadores y los iteradores que devuelven los iterables comunes de Python se comportan de forma muy similar, pero hay algunas diferencias importantes que conviene tener en cuenta:

  • Se evalúan de forma perezosa; la iteración es unidireccional y no hay forma de «retroceder» a un valor anterior.
  • Se consumen al iterar sobre los valores devueltos; no se reinician ni se guardan en memoria.
  • No se pueden ordenar ni invertir.
  • No son tipos de secuencia y no tienen indexes. No puedes referenciar un valor anterior o futuro mediante suma o resta, y no puedes usar la notación de corchetes ([]) ni el slicing.
  • No se pueden usar con la función len(), ya que no tienen longitud.
  • Pueden ser finitos o infinitos; ¡ten cuidado al recopilar todos los valores de un generator-iterator infinito!

Para usar el generador squares_generator():

>>> squared_numbers = squares_generator([1, 2, 3, 4])

>>> for square in squared_numbers:
...     print(square)
...
1
4
9
16

Los valores dentro de un generador también se pueden producir o acceder mediante la función next(). next() llama al método __next__() de un objeto generador, «avanzando» o evaluando el código del generador hasta su expresión yield, que entonces «produce» o devuelve el valor.

>>> squared_numbers = squares_generator([1, 2])

>>> next(squared_numbers)
1
>>> next(squared_numbers)
4

Cuando un generator se consume por completo y no le quedan más valores por devolver, lanza un error StopIteration.

>>> next(squared_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Diferencia entre iterables y generadores

Los generadores son un subconjunto especial de iteradores. Los Iterators son el mecanismo o protocolo que permite iterar sobre iterables. Los generadores y los iteradores que devuelven los iterables comunes de Python se comportan de forma muy similar, pero hay algunas diferencias importantes que conviene tener en cuenta:

  • Los generadores son unidireccionales; no hay forma de «retroceder» a un valor anterior.

  • Iterar sobre los generadores consume los valores devueltos; no se reinician.

  • Los generadores (al evaluarse de forma perezosa) no se pueden ordenar ni invertir.

  • Los generadores no tienen indexes, así que no puedes referenciar un valor anterior o futuro mediante suma o resta.

  • Los generadores no se pueden usar con la función len().

  • Los generadores pueden ser finitos o infinitos; ten cuidado al recopilar todos los valores de un generador infinito.

La expresión yield

La expresión yield es muy similar a la expresión return.

A diferencia de la expresión return, yield entrega valores a quien la llama en un punto específico, suspendiendo la evaluación o el retorno de cualquier valor adicional hasta que se soliciten.

Cuando se evalúa yield, se pausa la ejecución de la función que la contiene y se devuelven los valores de la función en ese momento.

La función entonces permanece en el ámbito, y cuando se llama a __next__(), la ejecución se reanuda hasta que se encuentra yield de nuevo.

Note

El uso de expresiones yield está prohibido fuera de las funciones.

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1

¿Por qué usar generadores?

Los generadores son útiles en muchas aplicaciones.

Cuando trabajas con una colección grande, quizás no quieras poner todos sus valores en memory. Un generador se puede usar para trabajar con datos más grandes poco a poco, ahorrando memoria y mejorando el rendimiento.

Los generadores también son muy útiles cuando un proceso o cálculo es complejo, costoso o infinito:

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

Ahora, cada vez que se llama a __next__() en el objeto infinite_sequence, devolverá el número anterior + 1.

Editar en GitHub El enlace se abre en una ventana o pestaña nueva

Aprende Generadores

La práctica está bloqueada

Desbloquea 2 ejercicios más para practicar Generadores