Itertools.tee на сопрограмме?

У меня есть древовидная структура объектов. Мне нужно перебирать все элементы ( "значения" ) в листьях. Для этого я сейчас использую методы генератора, как показано ниже:

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)

Отпечатки:

1
1
2
2
3
3

Теперь значения, возвращаемые Leaf, будут зависеть от внешнего параметра. Я думал о том, чтобы использовать сопрограммы, чтобы передать этот параметр до листовых узлов:

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

Этот код далеко не хорош, но он работает. Он печатает:

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

Однако проблема с этим решением. Я использовал itertools.teechain) для того, чтобы легко сохранить состояние итератора в случае, если мне нужно отступить. Я надеялся, что они будут работать и на сопрограммах, но, увы, нет такой удачи.

Некоторые альтернативные решения, которые я рассматриваю на данный момент:

  • У генераторов есть функции (замыкания), которые принимают внешний параметр
  • писать пользовательские классы для эмуляции сопрограмм с возможностями сохранения состояния

Первый вариант кажется самым привлекательным. Но, возможно, есть лучшие варианты?


В некотором контексте: я использую эту конструкцию в RinohType, где дерево сформировано с помощью MixedStyledText (node) и SingleStyledText (leaf ) объекты. Методы spans() дают экземпляры SingleStyledText. Последнее может зависеть от внешних параметров. Например, номер страницы, на которую они отображаются. В настоящее время они рассматриваются как особый случай.

Ответы

Ответ 1

Насколько это возможно, мне нравится, чтобы функции возвращали простые структуры данных. В этом случае

  • Я хотел бы сделать каждый node простым словарем (или в RinohType случае, SingleStyledTextConfig object или namedtuple). Эта реализация будет отлично работать с itertools, как и раньше, поскольку она просто преобразует данные в другие данные.
  • Я бы преобразовал эту коллекцию объектов в учетную запись для внешних параметров (например, номер страницы).
  • Наконец, я бы использовал набор данных конфигурации для фактического создания вывода (в RinohType случае, объект SingleStyledText).

На более высоком уровне: ваше предлагаемое решение (как я понимаю, ваша упрощенная версия) пытается сделать слишком много материала в то же время. Он пытается настроить создание объектов, настроить эту конфигурацию и создать объекты на основе этой конфигурации за один шаг. Ваша реализация будет проще, лучше сыграйте с itertools, и вам будет легче протестировать, если вы отделите эти проблемы.

Более подробное рассмотрение такого рода мышления см. в разделе Грани Гэри Бернхардта и Брэндон Роудс говорит о чистой архитектуре в Python (и, конечно же, ресурсы, которые они упоминают в ходе переговоров).

Ответ 2

Это начало второго варианта

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                    

Ответ 3

Я не уверен, правильно ли я понял вопрос. Нужно ли для Leaf2 делать вычисления? Если нет, то вы можете сделать:

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)))

Если действительно важно, чтобы Leaf2 выполнял обработку, вы могли бы сделать

class Leaf2:
    def values(self):
        for i in range(2):
            yield self

def process(self, parameter):
    pass

Итак, в основном вы можете сделать

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