permutations pairwise library groupby example python generator itertools coroutine tee

python - pairwise - itertools.tee en una coroutine?



pairwise python (3)

Tengo una estructura arbórea de objetos. Necesito iterar sobre todos los elementos ("valores") en las hojas. Para esto actualmente estoy usando métodos generadores como se ilustra a continuación:

class Node(object): def __init__(self): self.items = [Leaf(1), Leaf(2), Leaf(3)] def values(self): for item in self.items: for value in item.values(): yield value class Leaf(object): def __init__(self, value): self.value = value def values(self): for i in range(2): yield self.value n = Node() for value in n.values(): print(value)

Esto imprime:

1 1 2 2 3 3

Ahora, los valores devueltos por una Leaf dependerán de un parámetro externo. Estaba pensando en emplear coroutines para poder pasar este parámetro a los nodos de la hoja:

import itertools class Node2(object): def __init__(self): self.items = [Leaf2(1), Leaf2(2), Leaf2(3)] def values(self): parameter = yield for item in self.items: item_values = item.values() next(item_values) # advance to first yield try: while True: parameter = (yield item_values.send(parameter)) except StopIteration: pass class Leaf2(object): def __init__(self, value): self.value = value def values(self): parameter = yield try: for i in range(2): parameter = (yield ''{}{}''.format(self.value, parameter)) except StopIteration: pass n2 = Node2() values2 = n2.values() next(values2) # advance to first yield try: for i in itertools.count(ord(''A'')): print(values2.send(chr(i))) except StopIteration: pass

Este código está lejos de ser bonito, pero funciona. Se imprime:

1A 1B 2C 2D 3E 3F

Sin embargo, hay un problema con esta solución. Estaba usando itertools.tee (y chain ) ampliamente para guardar fácilmente el estado del iterador en caso de que tuviera que retroceder. Esperaba que esto también funcionara en las coroutinas, pero desgraciadamente no hubo tanta suerte.

Algunas soluciones alternativas que estoy considerando en este momento:

  • hacer que los generadores rindan funciones (cierres) que acepten el parámetro externo
  • escriba clases personalizadas para emular coroutines con capacidades para guardar el estado

La primera opción parece la más atractiva. Pero tal vez hay mejores opciones?

Algún contexto: estoy usando esta construcción en RinohType donde el árbol está formado por los MixedStyledText (node) y SingleStyledText (leaf). Los métodos SingleStyledText spans() producen instancias de SingleStyledText . Este último puede depender de parámetros externos. Por ejemplo, el número de página a la que se están procesando. Estos son actualmente tratados como un caso especial.


En la medida de lo posible, me gusta hacer que las funciones devuelvan estructuras de datos simples. En este caso,

  • Haría que cada nodo RinohType un diccionario simple (o, en el caso de RinohType , un objeto SingleStyledTextConfig o namedtuple ). Esta implementación funcionará muy bien con itertools , como antes, ya que simplemente transforma los datos en otros datos.
  • Transformaría esta colección de objetos para tener en cuenta los parámetros externos (como el número de página).
  • Finalmente, usaría la recopilación de datos de configuración para crear realmente la salida (en el caso de RinohType , el objeto SingleStyledText ).

En un nivel superior: su solución propuesta (como entiendo su versión simplificada) está tratando de hacer muchas cosas al mismo tiempo . Se trata de configurar la creación de objetos, ajustar esa configuración y crear objetos basados ​​en esa configuración en un solo paso. Su implementación será más sencilla, juegue mejor con itertools y será más fácil de probar si separa estas preocupaciones.

Para un tratamiento más detallado de este tipo de pensamiento, vea la charla Los límites de Gary Bernhardt y la charla de Brandon Rhodes sobre la arquitectura limpia en Python (y, por supuesto, los recursos que mencionan en las charlas).


Este es un comienzo en la segunda opción.

from types import GeneratorType def gen_wrapper(func): def _inner(*args): try: if args: if isinstance(args[0], GeneratorType): func.gen = getattr(func, ''gen'', args[0]) func.recall = next(func.gen) try: return func.recall except AttributeError: func.recall = next(func.gen) return func.recall except StopIteration: pass return _inner @gen_wrapper def Gen_recall(*args): pass


No estoy seguro si entiendo la pregunta correctamente. ¿Es necesario que Leaf2 haga los cálculos? Si no, entonces puedes hacer:

import itertools class Node2(object): def __init__(self): self.items = [Leaf2(1), Leaf2(2), Leaf2(3)] def values(self): for item in self.items: for value in item.values(): yield value class Leaf2(object): def __init__(self, value): self.value = value def values(self): for i in range(2): yield self.value def process(i, parameter): return ''{}{}''.format(i, parameter) n = Node2() for value, parameter in zip(n.values(), itertools.count(ord(''A''))): print(process(value, chr(parameter)))

Si es realmente importante que Leaf2 haga el procesamiento, usted podría hacer

class Leaf2: def values(self): for i in range(2): yield self def process(self, parameter): pass

Para que en lo principal se pueda hacer.

n = Node2() for node, parameter in zip(n.values(), itertools.count(ord(''A''))): print(node.process(chr(parameter)))