Saltar a contenido
 No os perd谩is mi futuro contenido, seguidme en y Youtube 馃榿

Funciones generadoras

Por regla general, cuando queremos crear una lista de alg煤n tipo, lo que hacemos es crear la lista vac铆a, y luego con un bucle varios elementos e ir a帽adiendolos a la lista si cumplen una condici贸n:

Note

[numero for numero in [0,1,2,3,4,5,6,7,8,9,10] if numero % 2 == 0 ]
[0, 2, 4, 6, 8, 10]

Tambi茅n vimos c贸mo era posible utilizar la funci贸n range() para generar din谩micamente la lista en la memoria, es decir, no ten铆amos que crearla en el propio c贸digo, sino que se interpretaba sobre la marcha:

Note

[numero for numero in range(0,11) if numero % 2 == 0 ]
[0, 2, 4, 6, 8, 10]

La verdad es que range() es una especie de funci贸n generadora. Por regla general las funciones devolvuelven un valor con return, pero la preculiaridad de los generadores es que van cediendo valores sobre la marcha, en tiempo de ejecuci贸n.

La funci贸n generadora range(0,11), empieza cediendo el 0, luego se procesa el for comprobando si es par y lo a帽ade a la lista, en la siguiente iteraci贸n se cede el 1, se procesa el for se comprueba si es par, en la siguiente se cede el 2, etc.

Con esto se logra ocupar el m铆nimo de espacio en la memoria y podemos generar listas de millones de elementos sin necesidad de almacenarlos previamente.

Veamos a ver c贸mo crear una funci贸n generadora de pares:

Note

def pares(n):
    for numero in range(n+1):
        if numero % 2 == 0:
            yield numero

pares(10)
<generator object pares at 0x000002945F38BFC0>

Como vemos, en lugar de utilizar el return, la funci贸n generadora utiliza el yield, que significa ceder. Tomando un n煤mero busca todos los pares desde 0 hasta el n煤mero+1 sirvi茅ndonos de un range().

Sin embargo, fijaros que al imprimir el resultado, 茅ste nos devuelve un objeto de tipo generador.

De la misma forma que recorremos un range() podemos utilizar el bucle for para recorrer todos los elementos que devuelve el generador:

Note

for numero in pares(10):
    print(numero)
0
2
4
6
8
10

Utilizando comprensi贸n de listas tambi茅n podemos crear una lista al vuelo:

Note

[numero for numero in pares(10)]
[0, 2, 4, 6, 8, 10]

Sin embargo el gran potencial de los generadores no es simplemente crear listas, de hecho como ya hemos visto, el propio resultado no es una lista en s铆 mismo, sino una secuencia iterable con un mont贸n de caracter铆sticas 煤nicas.

Iteradores

Por tanto las funciones generadoras devuelven un objeto que suporta un protocolo de iteraci贸n. 驴Qu茅 nos permite hacer? Pues evidentemente controlar el proceso de generaci贸n. Teniendo en cuenta que cada vez que la funci贸n generadora cede un elemento, queda suspendida y se retoma el control hasta que se le pide generar el siguiente valor.

As铆 que vamos a tomar nuestro ejemplo de pares desde otra perspectiva, como si fuera un iterador manual, as铆 veremos exactamente a lo que me refiero:

Note

pares = pares(3)

Bien, ahora tenemos un iterador de pares con todos los n煤meros pares entre el 0 y el 3. Vamos a conseguir el primer n煤mero par:

Note

next(pares)
0

Como vemos la funci贸n integrada next() nos permite acceder al siguiente elemento de la secuencia. Pero no s贸lo eso, si volvemos a ejecutarla...

Note

next(pares)
2

Ahora devuelve el segundo! 驴No os recuerdo esto al puntero de los ficheros? Cuando le铆amos una l铆nea, el puntero pasaba a la siguiente y as铆 sucesivamente. Pues aqu铆 igual.

驴Y qu茅 pasar铆a si intentamos acceder al siguiente, a煤n sabiendo que entre el 0 y el 3 s贸lo tenemos los pares 0 y 2?

Note

next(pares)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-34-68378216ba43> in <module>()
----> 1 next(pares)

StopIteration: 

Pues que nos da un error porque se ha acabado la secuencia, as铆 que tomad nota y capturad la excepci贸n si v谩is a utilizarlas sin saber exactamente cuantos elementos os devolver谩 el generador.

As铆 que la pregunta que nos queda es 驴s贸lo es posible iterar secuencias generadas al vuelo? Vamos a probar con una lista:

Note

lista = [1,2,3,4,5]
next(lista)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-38-28c22b67c419> in <module>()
    1 lista = [1,2,3,4,5]
----> 2 next(lista)
    3 
    4 cadena = "Hola"
    5 next(cadena)

TypeError: 'list' object is not an iterator

驴Quiz谩 con una cadena?

Note

cadena = "Hola"
next(cadena)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-39-44ca9ed1903b> in <module>()
    1 cadena = "Hola"
----> 2 next(cadena)

TypeError: 'str' object is not an iterator

Pues no, no podemos iterar ninguna colecci贸n como si fuera una secuencia. Sin embargo, hay una funci贸n muy interesante que nos permite covertir las cadenas y algunas colecciones a iteradores, la funci贸n iter():

Note

lista = [1,2,3,4,5]
lista_iterable = iter(lista)
print( next(lista_iterable) )
print( next(lista_iterable) )
print( next(lista_iterable) )
print( next(lista_iterable) )
print( next(lista_iterable) )
1
2
3
4
5

Note

cadena = "Hola"
cadena_iterable = iter(cadena)
print( next(cadena_iterable) )
print( next(cadena_iterable) )
print( next(cadena_iterable) )
print( next(cadena_iterable) )
H
o
l
a

Con esto hemos visto las bases, os sugiero probar por vuestra cuenta m谩s colecciones a ver si encontr谩is alguna m谩s que se pueda iterar.


脷ltima edici贸n: 6 de Octubre de 2018