Nuevas Posibilidades de los Generadores

Python 2.5 añade la posibilidad de pasar valores a un generador. Pero antes, recordemos qué es, y para qué sirve, un generador. Los generadores están con nosotros desde Python 2.3 y, básicamente, son funciones cuyo resultado es un iterador.

¿Y qué es, y para qué sirve, un iterador, se preguntará el astuto lector?

Los iteradores son contenedores, capaces de devolver sus elementos de uno en uno. Los iteradores se pueden usar en un bucle for y en otros lugares donde se necesite una secuencia, como por ejemplo, en las funciones zip(), map() o lambda() reduce(). Son iteradores todos los tipos de secuencia (como list, str y tuple), algunos otros tipos, como dict y file, y cualquier objeto cuya clase defina los métodos __iter__() y __getitem__().

El generador, decíamos, es como una función normal, solo que retorna un iterador. La única diferencia aparente entre una función normal y un generador es que se usa la palabra reservada yield en vez de return. Las funciones generadoras suelen contener uno o más bucles que devuelven (yield) elementos al llamante. La ejecución de la función se para en la palabra clave yield (devolviendo el resultado) y se reanuda cuando se solicita el siguiente elemento llamando al método next() del iterador devuelto.

Los generadores son, por tanto, una herramienta simple y potente para crear iteradores. Cada vez que se quiere obtener un nuevo elemento, el generador reanuda la ejecución donde se quedó (recordando todos los valores de los datos y la última sentencia en ejecutarse). Este ejemplo muestra lo fácil que es crear generadores:

def delreves(data):
xxxfor indice in range(len(datos)-1, -1, -1):
xxxxxxyield datos[indice]


for car in delreves('Wanda'):
...xxxxprint car
...
a
d
n
a
W

Cualquier cosa que se pueda hacer con generadores se puede hacer también definiendo una clase iterador. Lo único que aportan los generadores es que su métodos __iter__() y next() se crean automáticamente. Además, como las variables locales y el estado de ejecución se guardan automáticamente entre llamadas, la función generadora es más fácil de escribir y no requiere utilizar variables de instancia como self.indice o self.datos. Cuando los generadores terminan su ejecución, hacen saltar la excepción StopIteration automáticamente. La combinación de estas características hace que la creación de iteradores sea tan sencilla como la de una función normal.

En fin, más información, y más detallada sobre estos temas, se puede consultar aquí: Iteradores y aquí: Generadores.

Nuevas posibilidades

Los generadores antes de Python 2.5 sólo producían salidas; una vez realizada la primera llamada, (momento en el cual se crea el iterador), no hay forma de añadir nueva información a la función generadora. En determinados casos, la posibilidad de alterar el comportamiento de la función de una determinada manera durante la secuencia de llamadas puede ser útil. Algunas soluciones para este problema en el momento actual incluyen utilizar variables globales (¡Aurghhh!) o pasarle a la función un objeto mutable, que pueda ser modificado por el llamador de la función.

Veamos como podremos hacerlo a partir de Python 2.5 con un ejemplo. Supongamos el siguiente generador:

def counter (maximum):
xxxi = 0
xxxwhile i < maximum:
xxxxxxyield i
xxxxxxi += 1

En este punto, debería estar claro que una llamada a counter(9), por ejemplo, devolvería consecutivamente los valores desde 0 hasta 9.

A partir de Python 2.5, yield pasa de ser una sentencia a ser una expresión; en otras palabras, yield puede retornar un valor, que puede ser utilizado o asignado a una variable.

val = (yield i)

Nota: Es recomendable rodear con paréntesis la expresión yield, siempre que se utiliza para algo, como en el ejemplo anterior en que es asignado a una variable. No son siempre necesarios, pero es más fácil añadirlos siempre que aprenderse los casos especiales en que no son necesarios (De todas formas, si se es lo suficientemente aguerrido, las reglas exactas están explicadas en el PEP 342 – Coroutines via Enhanced Generators)

Los valores son enviados al generador llamando al método send(value) del generador (Recordemos que en Python, casi todo es un objeto, incluyendo las funciones). Cuando se llama a send, se continua con la ejecución del código del generador, y la expresión yield retorna el valor especificado. Si se llama al método normal, next(), yield retorna None.

En el siguiente ejemplo, modificamos el código para permitirnos cambiar el valor del contador en medio de las sucesivas llamadas.

def counter (maximum):
xxxi = 0
xxxwhile i < maximum:
xxxxxxval = (yield i)
xxxxxx# If value provided, change counter
xxxxxxif val is not None:
xxxxxxxxxi = val
xxxxxxelse:
xxxxxxxxxi += 1

Con lo cual podriamos hacer lo siguiente:

>>> it = counter(10)
>>> print it.next()
0
>>> print it.next()
1
>>> print it.send(8)
8
>>> print it.next()
9
>>> print it.next()
Traceback (most recent call last):
xxxFile ``t.py'', line 15, in ?
xxxxxxprint it.next()
StopIteration

Además de send(value), hay dos nuevos métodos en los generadores:



Deja un comentario

Fill in your details below or click an icon to log in:

Logo de WordPress.com

You are commenting using your WordPress.com account. Log Out / Cambiar )

Twitter picture

You are commenting using your Twitter account. Log Out / Cambiar )

Facebook photo

You are commenting using your Facebook account. Log Out / Cambiar )

Connecting to %s


About

s1d4rt4 es un niño muy mañoso y enojón, que a pesar de haber leído como un engendro del mal en sus primeros años de vida, la mala ortografía le ha perseguido cual jornalero un descanso. Afortunadamente para él, hoy tiene acceso a un pc con corrector ortográfico xD. More...

del.icio.us


Seguir

Get every new post delivered to your Inbox.