2011-06-13 4 views
5

Estoy trabajando en un problema que implica la validación de un formato desde un parche diff unificado.¿Cuál es una buena manera de decorar un iterador para alterar el valor antes de llamar al siguiente en python?

Las variables dentro del formato interno pueden abarcar varias líneas a la vez, así que escribí un generador que extrae cada línea y produce la variable cuando se completa.

Para evitar tener que volver a escribir esta función al leer desde un archivo diff unificado, creé un generador para quitar los caracteres unificados de la línea antes de pasarlo al validador de formato interno. Sin embargo, me estoy estancando en un ciclo infinito (tanto en el código como en mi cabeza). Me he abstraído del problema al siguiente código. Estoy seguro de que hay una mejor manera de hacer esto. Simplemente no sé qué es.

from collections import Iterable 

def inner_format_validator(inner_item): 
    # Do some validation to inner items 
    return inner_item[0] != '+' 

def inner_gen(iterable): 
    for inner_item in iterable: 
     # Operates only on inner_info type data 
     yield inner_format_validator(inner_item) 

def outer_gen(iterable): 
    class DecoratedGenerator(Iterable): 
     def __iter__(self): 
      return self 
     def next(self): 
      # Using iterable from closure 
      for outer_item in iterable: 
       self.outer_info = outer_item[0] 
       inner_item = outer_item[1:] 
       return inner_item 
    decorated_gen = DecoratedGenerator() 
    for inner_item in inner_gen(decorated_gen): 
     yield inner_item, decorated_gen.outer_info 

if __name__ == '__main__':  
    def wrap(string): 
     # The point here is that I don't know what the first character will be 
     pseudo_rand = len(string) 
     if pseudo_rand * pseudo_rand % 2 == 0: 
      return '+' + string 
     else: 
      return '-' + string 

    inner_items = ["whatever"] * 3 
    # wrap screws up inner_format_validator 
    outer_items = [wrap("whatever")] * 3 
    # I need to be able to 
    # iterate over inner_items 
    for inner_info in inner_gen(inner_items): 
     print(inner_info) 
    # and iterate over outer_items 
    for outer_info, inner_info in outer_gen(outer_items): 
     # This is an infinite loop 
     print(outer_info) 
     print(inner_info) 

¿Alguna idea de una forma mejor y más piadosa de hacer esto?

Respuesta

2

Me gustaría hacer algo más simple, como esto:

def outer_gen(iterable): 

    iterable = iter(iterable) 
    first_item = next(iterable) 
    info = first_item[0] 

    yield info, first_item[1:] 

    for item in iterable: 
     yield info, item 

Esto ejecutará las 4 primeras líneas de una sola vez, y luego entrar en la serie y producir lo que quiere.

Es probable que desee agregar try/except a cacth IndexErrors aquí y allá.

Si desea tomar valores, mientras que comienzan con algo o por el contrario, recuerda que puedes utilizar una gran cantidad de cosas de la itertools caja de herramientas, y en particular dropwhile, takewhile y chain:

>>> import itertools 
>>> l = ['+foo', '-bar', '+foo'] 
>>> list(itertools.takewhile(lambda x: x.startswith('+'), l)) 
['+foo'] 
>>> list(itertools.dropwhile(lambda x: x.startswith('+'), l)) 
['-bar', '+foo'] 
>>> a = itertools.takewhile(lambda x: x.startswith('+'), l) 
>>> b = itertools.dropwhile(lambda x: x.startswith('+'), l) 
>>> list(itertools.chain(a, b)) 
['+foo', '-bar', '+foo'] 

Y recuerda que puede crear generadores como listas de comprensión, almacenarlos en las variables y la cadena de ellos, igual que lo haría comandos de Linux: tubería

import random 

def create_item(): 
    return random.choice(('+', '-')) + random.choice(('foo', 'bar')) 

random_items = (create_item() for s in xrange(10)) 
added_items = ((i[0], i[1:]) for i in random_items if i.startswith('+')) 
valid_items = ((prefix, line) for prefix, line in added_items if 'foo' in line) 

print list(valid_items) 

Con todo esto, usted debe estar capaz de encontrar alguna forma pitónica para resolver su problema :-)

+0

Creo que el último ejemplo es lo que estoy buscando. –

1

Creo que va a hacer lo que pretende si cambia la definición de DecoratedGenerator a esto:

class DecoratedGenerator(Iterable): 
    def __iter__(self): 
     # Using iterable from closure 
     for outer_item in iterable: 
      self.outer_info = outer_item[0] 
      inner_item = outer_item[1:] 
      yield inner_item 

Su versión original nunca se terminó debido a su método next() era sin estado y le devolverá el mismo valor cada vez que se llamado. Sin embargo, no necesitó tener un método next(); puede implementar __iter__() usted mismo (como hice yo), y luego todo funciona bien.

2

Todavía no les gusta mucho esto, pero al menos es más corto y un poco más Pythonic:

from itertools import imap, izip 
from functools import partial 

def inner_format_validator(inner_item): 
    return not inner_item.startswith('+') 

inner_gen = partial(imap, inner_format_validator) 

def split(astr): 
    return astr[0], astr[1:] 

def outer_gen(iterable): 
    outer_stuff, inner_stuff = izip(*imap(split, iterable)) 
    return izip(inner_gen(inner_stuff), outer_stuff) 

[EDIT] inner_gen() y outer_gen() sin IMAP y parcial:

def inner_gen(iterable): 
    for each in iterable: 
     yield inner_format_validator(each) 

def outer_gen(iterable): 
    outer_stuff, inner_stuff = izip(*(split(each) for each in iterable)) 
    return izip(inner_gen(inner_stuff), outer_stuff) 

Tal vez esta sea una solución mejor, aunque diferente:

def transmogrify(iter_of_iters, *transmogrifiers): 
    for iters in iter_of_iters: 
     yield (
      trans(each) if trans else each 
       for trans, each in izip(transmogrifiers, iters) 
     ) 

for outer, inner in transmogrify(imap(split, stuff), inner_format_validator, None): 
    print inner, outer 
+0

Gracias por el ejemplo de cómo usar 'functools.partial'.Las herramientas son bastante ingeniosas, pero encuentro que esta solución es un poco difícil de seguir. ¿Habría algún beneficio de rendimiento al hacerlo de esta manera? –

+0

Un problema es el argumento de desempaquetado en 'izip (* imap (split, iterable))' que descomprime todo el iterable dividido en memoria a la vez. Ver mi otra solución para una alternativa que evita esto. Creo que es bastante pitónico. – pillmuncher

Cuestiones relacionadas