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.tee
(и chain
) для того, чтобы легко сохранить состояние итератора в случае, если мне нужно отступить. Я надеялся, что они будут работать и на сопрограммах, но, увы, нет такой удачи.
Некоторые альтернативные решения, которые я рассматриваю на данный момент:
- У генераторов есть функции (замыкания), которые принимают внешний параметр
- писать пользовательские классы для эмуляции сопрограмм с возможностями сохранения состояния
Первый вариант кажется самым привлекательным. Но, возможно, есть лучшие варианты?
В некотором контексте: я использую эту конструкцию в 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)))