Pipelines en Python utilizando generators
Posted on | Junio 1, 2009 |
Ya el libro que me estoy leyendo me empieza a develar algunas bondades de python que este humilde programador de java no conocía: los iterators y generators.
Para un javero como yo los iterators de python son un poco raros, pues en realidad forman parte del lenguaje, o dicho de otra manera, el lenguaje “soporta” el patrón iterator, básicamente nuestra clase iterator debe implementar los métodos next() y __iter__(); el primero es el que manipula el paso como tal, y el segundo devuelve el objeto iterator:
class MyIterator(object):
def __init__(self,paso):
self.paso=paso
def next():
"""Returns the next element."""
if self.step == 0:
raise StopIteration
self.step -= 1
return self.step
def __iter__(self):
"""Returns the iterator itself."""
return self
if __name__ == '__main__':
for el in MyIterator(4):
print el
Este ejemplo, y en general todo lo que voy a escribir hoy, está sacado de Expert Python Programming.
Como pueden ver, si están acostumbrados a la interfaz java.util.Iterator de java, esto les debe parecer una bobería… pero, ahora viene lo bueno, los generators.
Los iterators son la base de los generators, yo no voy a hablar extensamente de los generators, pues recién ahora estoy enterándome que existen, pero vale la pena ver como hacer lo que dice el título de este artículo: hacer pipelines.
Resulta que quizás algunos de nosotros(los de java) nos hayamos acostumbrado a ir recorriendo una colección de objetos(antes con un java.util.terator, ahora con un for gracias a los generics), cuando hemos necesitado hacerles operaciones a sus elementos, en python es otra historia, pues además de la función map(), tenemos los generators
Un generator utiliza la directiva yield para detener la ejecución, a la par que se hace el procesamiento que haya que hacerle al elemento en cuestión, y queda en espera de una nueva llamada a next(), consideremos lo siguiente:
def power(values):
for value in values:
print 'powering %s' % value
yield value * 2
def adder(values):
for value in values:
print 'adding to %s' % value
if value % 2 ==0:
yield value + 3
else:
yield value +2
Aquí tenemos 2 funciones, una multiplica por dos a todos los elementos de una colección, y la otra, en dependencia si el elemento es par o no, le suma 2 o 3. Lo que haremos ahora será pasarle una lista de numeros a la funcion power(), y su resultado a la función adder():
In [66]: res = adder(power(elms)) In [67]: [res.next() for l in elms] powering 1 adding to 2 powering 4 adding to 8 powering 7 adding to 14 powering 9 adding to 18 powering 12 adding to 24 powering 19 adding to 38 Out[67]: [5, 11, 17, 21, 27, 41]
En realidad lo que se le pasa a la función adder() es un generator, por lo que al recorrer el resultado mediante next() lo que obtenemos es un generator que apunta a los valores procesados por ambas funciones.
Para que se entienda mejor:
In [73]: g = power(elms) In [74]: g Out[74]: generator object power at 0x2d96f50 In [75]: res2 = adder(g) In [76]: res2 Out[76]: generator object adder at 0x2da3190 In [77]: [res2.next() for l in elms] powering 1 adding to 2 powering 4 adding to 8 powering 7 adding to 14 powering 9 adding to 18 powering 12 adding to 24 powering 19 adding to 38 Out[77]: [5, 11, 17, 21, 27, 41]
Anjá, cada función devuelve un objeto generator, que puede ser consumido por otra función y así ad infinitum…
Espero que les sirva de algo, pronto seguiré profundizando en los generators y en su papel en las coroutines.
Comments
Leave a Reply