【问题标题】:Restart Generator that Consumes a Refillable Iterator重新启动使用可再填充迭代器的生成器
【发布时间】:2017-06-02 12:31:22
【问题描述】:

我在使用使用可再填充迭代器的生成器时遇到问题。

这是我的简单生成器:

def hi(iterable):
  for val in iterable:
    yield val

我传递给 hi 生成器的可迭代对象是来自 functional_pipes repo 的 Reservoir 类,它可以在其元素耗尽后重新填充。

我想使用 hi 生成器,直到 StopIteration 被引发,然后重新填充 iterable,然后再次使用它

refillable = Reservoir((1, 2, 3, 4))
hi_iter = hi(refillable)

print(tuple(hi_iter))

refillable((5, 6, 7, 8))
print(tuple(hi_iter))

但这会打印出来

(1, 2, 3, 4)
()

第二个元组也应该是 (5, 6, 7, 8)。

我找到的唯一解决方案是用一个类包装 hi 生成器

def super_gener(function):
  class wrapper_class:
    def __init__(self, iterable):
      self.iterable = iterable
      self.zipped = None

    def __iter__(self):
      return self

    def __next__(self):
      try:
        return next(self.zipped)

      except TypeError:
        self.zipped = function(self.iterable)
        return next(self)

      except StopIteration as err:
        self.zipped = None
        raise err

  return wrapper_class

hi_iter = super_gener(hi)(refillable)

print(tuple(hi_iter))
refillable(data)
print(tuple(hi_iter))

这个解决方案似乎有点过分,我正在寻找一个更简单的解决方案。感谢您的帮助。

回应 Ptank: 我无法将 iterable 保存到元组,因为 iterable 并不总是产生相同的项目,并且在第二次填充 refillable 之前这些项目是未知的。

【问题讨论】:

    标签: python generator


    【解决方案1】:

    恐怕唯一的解决方案可能是创建一个可重新填充的生成器包装类。编辑:原始未经测试的代码不起作用。我现在重构了下面的想法并进行了测试。

    此对象将引发StopIteration ONCE,之后它将重新启动。它旨在与Resettable 装饰器一起使用,该装饰器将_func 属性添加到类。它应该具有原始生成器的所有相同功能。

    class ResettableGenerator():
        '''Generator wrapper that is resettable.'''
        def __init__(self, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs
            self.start()
        def __next__(self):
            n = self.send(None)
            return n
        def __iter__(self):
            yield from self._gen
        def start(self):
            self._gen = self._func(*self.args, **self.kwargs)
        def send(self, *args, **kwargs):
            try:
                n = self._gen.send(*args, **kwargs)
                return n
            except StopIteration:
                self.start()
                raise
        def throw(self, *args, **kwargs):
            self._gen.throw(*args, **kwargs)
        def close(self):
            self._gen.close()
    

    这里是装饰器:

    def Resettable(some_func):
        cls = type(some_func.__name__, (ResettableGenerator,), {})
        cls._func = staticmethod(some_func)
        return cls
    

    像这样使用它:

    @Resettable
    def f():
        yield 1
    

    现在你可以这样做了:

    >>> g=f()
    >>> next(g)
    1
    >>> next(g)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 8, in __next__
      File "<stdin>", line 16, in send
    StopIteration
    >>> next(g)
    1 #  generator has restarted itself
    

    【讨论】:

    • 感谢您的意见!我喜欢它有装饰器并且可以使用任何生成器。
    • 几年后在忘记它之后回到这个问题:我认为我今天的实施方式略有不同。但我想说创建一个包装类和使用它的装饰器可能仍然是最好的解决方案。
    【解决方案2】:

    您也许可以将hi 更新为生成器工厂,并在每次您想使用它时调用它。这比您当前的解决方案更干净,但由于其他原因可能不适用于您的情况。

    def hi(iterable):
        def wrapper():
            for val in iterable:
                yield val
        return wrapper
    

    与您当前使用的用法相似:

    refillable = Reservoir((1, 2, 3, 4))
    hi_iter = hi(refillable)
    print(tuple(hi_iter())) # (1, 2, 3, 4)
    
    refillable((5, 6, 7, 8))
    print(tuple(hi_iter())) # (5, 6, 7, 8)
    

    如果您想保持与原始示例完全相同的语法,您可以创建一个类似的瘦类。

    class hi:
        def __init__(self, iterable):
            self.__iter__ = lambda: iterable
    

    【讨论】:

    • 感谢贾里德的回答。不幸的是,在大多数情况下,我无法重新定义我将使用的生成器。
    【解决方案3】:

    因为生成器不是元组,所以生成的生成器是无记忆的,只能读取一次。

    只使用一个元组

    def hi(iterable):
        return tuple(iterable)
    

    【讨论】:

    • 我无法将 iterable 保存到元组,因为 iterable 并不总是产生相同的项目,并且在第二次填充 refillable 之前这些项目是未知的。
    猜你喜欢
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 2018-05-24
    • 1970-01-01
    • 2018-05-16
    • 1970-01-01
    • 2020-06-29
    • 1970-01-01
    相关资源
    最近更新 更多