关于协程的itertools.tee?

时间:2014-04-30 22:10:40

标签: python generator itertools coroutine 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(节点)和SingleStyledText(叶)对象组成。 spans()方法会生成SingleStyledText个实例。后者可以取决于外部参数。例如,它们被呈现的页码。这些目前被视为特例。

3 个答案:

答案 0 :(得分:1)

在可能的范围内,我喜欢让函数返回简单的数据结构。在这种情况下,

  • 我会让每个节点产生一个简单的字典(或者在RinohType的情况下,产生一个SingleStyledTextConfig对象或namedtuple)。与以前一样,此实现将与itertools很好地协作,因为它只是将数据转换为其他数据。
  • 我将这个对象集合转换为外部参数(如页码)。
  • 最后,我使用配置数据集合来实际创建输出(在RinohType的情况下,SingleStyledText对象)。

在更高级别:您建议的解决方案(据我了解您的简化版本)同时尝试做太多事情。它正在尝试配置对象创建,调整配置,并在一步中基于 配置创建对象。您的实现将更简单,使用itertools更好,并且如果您将这些问题分开,则更容易测试。

有关此类思维的更详细处理,请参阅Gary Bernhardt's Boundaries talkBrandon Rhodes' talk on the Clean Architecture in Python(当然还有他们在会谈中提到的资源)。

答案 1 :(得分:0)

这是第二个选项的开始

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                    

答案 2 :(得分:0)

我不确定我是否正确理解了这个问题。 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)))