协程中的itertools.tee是什么?

16
我有一个对象的树形结构。我需要迭代所有叶子节点中的所有项(“values”)。为此,我目前使用生成器方法,如下所示:
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实例。后者可以依赖于外部参数。例如,它们被渲染到的页面编号。目前将其视为特殊情况。


你使用的是哪个版本的Python? - Games Brainiac
@GamesBrainiac 我正在使用Python 3.4进行开发,但最终可能会回溯到2.7(为2.7和3.x使用单一代码库)。你问这个问题有什么特别的原因吗? - Brecht Machiels
听起来像是访问者模式可以应用的地方 - 你考虑过这个吗? - Captain Whippet
3个回答

1
尽可能地,我喜欢使函数返回简单的数据结构。在这种情况下,
  • 我会让每个节点产生一个简单的字典(或者在 RinohType 的情况下是 SingleStyledTextConfig 对象或 namedtuple)。由于它只是将数据转换为其他数据,所以这种实现将与 itertools 很好地配合。
  • 我会转换这些对象的集合以考虑外部参数(例如页码)。
  • 最后,我会使用配置数据的集合来实际创建输出(在 RinohType 的情况下,是 SingleStyledText 对象)。
在更高层次上:你提出的解决方案(就我理解的简化版本而言)试图同时做太多的事情。它试图在一步中配置对象创建、调整该配置并基于该配置创建对象。如果你分离这些问题,那么你的实现将更简单、更易于测试,并且与 itertools 更好地配合。

关于这种思维方式的更详细讨论,请参见Gary Bernhardt在Boundaries演讲中Brandon Rhodes关于Python中清晰架构的演讲(当然还有他们演讲中提到的资源)。


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                    

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接