【问题标题】:itertools.tee on a coroutine?itertools.tee 关于协程?
【发布时间】:2014-06-17 09:53:33
【问题描述】:

我有一个对象的树形结构。我需要遍历叶子中的所有项目(“值”)。为此,我目前正在使用如下图所示的生成器方法:

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?
  • @GamesBrainiac 我正在 Python 3.4 上开发,但最终可能会向后移植到 2.7(2.7 和 3.x 的单一代码库)。你问什么特别的原因?
  • 听起来像是可以应用访问者模式的地方——你考虑过吗?

标签: python generator itertools coroutine tee


【解决方案1】:

尽可能让函数返回简单的数据结构。在这种情况下,

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

在更高的层面上:您提出的解决方案(据我了解您的简化版本)试图同时做太多事情。它试图在一个步骤中配置对象创建、调整该配置并基于 that 配置创建对象。您的实现将更简单,与itertools 配合得更好,并且如果您将这些问题分开,则更容易测试。

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      这是第二个选项的开始

      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                    
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-03-05
        • 2020-03-23
        • 1970-01-01
        • 1970-01-01
        • 2012-10-22
        • 2020-07-08
        • 2015-12-11
        相关资源
        最近更新 更多