代码之家  ›  专栏  ›  技术社区  ›  Dewfy

在python中重置生成器对象

  •  120
  • Dewfy  · 技术社区  · 15 年前

    我有多个yield返回的generator对象。准备调用此生成器相当耗时。这就是为什么我要多次重用生成器。

    y = FunctionWithYield()
    for x in y: print(x)
    #here must be something to reset 'y'
    for x in y: print(x)
    

    当然,我考虑将内容复制到简单的列表中。

    15 回复  |  直到 6 年前
        1
  •  93
  •   funky-future euri10    7 年前

    另一种选择是使用 itertools.tee() 函数创建生成器的第二个版本:

    y = FunctionWithYield()
    y, y_backup = tee(y)
    for x in y:
        print(x)
    for x in y_backup:
        print(x)
    

    从内存使用的角度来看,如果原始迭代可能不会处理所有的项,那么这可能是有益的。

        2
  •  118
  •   Hank Gay    15 年前

    发电机不能重绕。您有以下选项:

    1. 再次运行发电机功能,重新启动发电:

      y = FunctionWithYield()
      for x in y: print(x)
      y = FunctionWithYield()
      for x in y: print(x)
      
    2. 将生成器结果存储在内存或磁盘上的数据结构中,您可以再次迭代:

      y = list(FunctionWithYield())
      for x in y: print(x)
      # can iterate again:
      for x in y: print(x)
      

    期权的缺点 1个 是它再次计算值。如果这是CPU密集型的话,你最终会计算两次。另一方面, 是仓库。整个值列表将存储在内存中。如果值太多,这可能是不实际的。

    所以你有经典的 内存与处理权衡 . 我无法想象一种在不存储值或重新计算值的情况下重绕生成器的方法。

        3
  •  28
  •   fbicknel    11 年前
    >>> def gen():
    ...     def init():
    ...         return 0
    ...     i = init()
    ...     while True:
    ...         val = (yield i)
    ...         if val=='restart':
    ...             i = init()
    ...         else:
    ...             i += 1
    
    >>> g = gen()
    >>> g.next()
    0
    >>> g.next()
    1
    >>> g.next()
    2
    >>> g.next()
    3
    >>> g.send('restart')
    0
    >>> g.next()
    1
    >>> g.next()
    2
    
        4
  •  24
  •   Aaron Digulla    15 年前

    可能最简单的解决方案是将昂贵的部件包装在一个对象中,并将其传递给生成器:

    data = ExpensiveSetup()
    for x in FunctionWithYield(data): pass
    for x in FunctionWithYield(data): pass
    

    这样,您就可以缓存昂贵的计算。

    如果可以同时将所有结果保存在RAM中,则使用 list() 将生成器的结果具体化为一个简单的列表并使用它。

        5
  •  13
  •   michaelsnowden    7 年前

    我想为一个老问题提供不同的解决方案

    class IterableAdapter:
        def __init__(self, iterator_factory):
            self.iterator_factory = iterator_factory
    
        def __iter__(self):
            return self.iterator_factory()
    
    squares = IterableAdapter(lambda: (x * x for x in range(5)))
    
    for x in squares: print(x)
    for x in squares: print(x)
    

    与类似的 list(iterator) 是这样吗? O(1) 空间复杂性和 列表(迭代器) O(n) .缺点是,如果您只能访问迭代器,而不能访问生成迭代器的函数,那么就不能使用此方法。例如, 做下面的事情似乎是合理的,但它不会起作用。

    g = (x * x for x in range(5))
    
    squares = IterableAdapter(lambda: g)
    
    for x in squares: print(x)
    for x in squares: print(x)
    
        6
  •  5
  •   Hank Gay    15 年前

    如果Grzegorzoledzki的回答不够,你可能会使用 send() 实现你的目标。见 PEP-0342 有关增强的生成器和yield表达式的更多详细信息。

    更新:另请参见 itertools.tee() . 它涉及到上面提到的一些内存与处理之间的权衡,但是它 可以 保存一些内存,而不只是存储生成器结果 list 这取决于你如何使用发电机。

        7
  •  3
  •   Shubham Chaudhary    8 年前

    official documentation of tee :

    通常,如果一个迭代器使用之前的大部分或全部数据 另一个迭代器启动,使用list()而不是tee()更快。

    所以最好用 list(iterable) 相反,在你的情况下。

        8
  •  3
  •   Ben Usman    7 年前

    如果您的生成器在某种意义上是纯的,它的输出只依赖于传递的参数和步骤号,并且您希望结果生成器可以重新启动,那么下面的排序片段可能很方便:

    import copy
    
    def generator(i):
        yield from range(i)
    
    g = generator(10)
    print(list(g))
    print(list(g))
    
    class GeneratorRestartHandler(object):
        def __init__(self, gen_func, argv, kwargv):
            self.gen_func = gen_func
            self.argv = copy.copy(argv)
            self.kwargv = copy.copy(kwargv)
            self.local_copy = iter(self)
    
        def __iter__(self):
            return self.gen_func(*self.argv, **self.kwargv)
    
        def __next__(self):
            return next(self.local_copy)
    
    def restartable(g_func: callable) -> callable:
        def tmp(*argv, **kwargv):
            return GeneratorRestartHandler(g_func, argv, kwargv)
    
        return tmp
    
    @restartable
    def generator2(i):
        yield from range(i)
    
    g = generator2(10)
    print(next(g))
    print(list(g))
    print(list(g))
    print(next(g))
    

    输出:

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    []
    0
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    1
    
        9
  •  2
  •   SMeznaric    9 年前

    可以定义返回生成器的函数

    def f():
      def FunctionWithYield(generator_args):
        code here...
    
      return FunctionWithYield
    

    现在,您可以随心所欲地执行以下操作:

    for x in f()(generator_args): print(x)
    for x in f()(generator_args): print(x)
    
        10
  •  1
  •   Eric Aya    8 年前

    没有重置迭代器的选项。迭代器迭代时通常会弹出 next() 功能。唯一的方法是在迭代迭代器对象之前进行备份。在下面检查。

    正在使用项0到9创建迭代器对象

    i=iter(range(10))
    

    遍历将弹出的next()函数

    print(next(i))
    

    将迭代器对象转换为列表

    L=list(i)
    print(L)
    output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    所以项目0已经弹出。当我们将迭代器转换为list时,所有的项都会弹出。

    next(L) 
    
    Traceback (most recent call last):
      File "<pyshell#129>", line 1, in <module>
        next(L)
    StopIteration
    

    因此,在开始迭代之前,需要将迭代器转换为用于备份的列表。 列表无法转换为迭代器 iter(<list-object>)

        11
  •  1
  •   pylang    7 年前

    您现在可以使用 more_itertools.seekable (第三方工具),用于重置迭代器。

    通过安装 > pip install more_itertools

    import more_itertools as mit
    
    
    y = mit.seekable(FunctionWithYield())
    for x in y:
        print(x)
    
    y.seek(0)                                              # reset iterator
    for x in y:
        print(x)
    

    注意:内存消耗随着迭代器的推进而增加,因此请注意大型iterables。

        12
  •  0
  •   ilya n.    15 年前

    我不知道你所说的昂贵的准备是什么意思,但我想你确实有

    data = ... # Expensive computation
    y = FunctionWithYield(data)
    for x in y: print(x)
    #here must be something to reset 'y'
    # this is expensive - data = ... # Expensive computation
    # y = FunctionWithYield(data)
    for x in y: print(x)
    

    如果是这样,为什么不重用呢? data 是吗?

        13
  •  0
  •   tvt173    8 年前

    好吧,你说你想多次调用一个生成器,但是初始化很昂贵…像这样的东西怎么样?

    class InitializedFunctionWithYield(object):
        def __init__(self):
            # do expensive initialization
            self.start = 5
    
        def __call__(self, *args, **kwargs):
            # do cheap iteration
            for i in xrange(5):
                yield self.start + i
    
    y = InitializedFunctionWithYield()
    
    for x in y():
        print x
    
    for x in y():
        print x
    

    或者,您可以创建自己的类,该类遵循迭代器协议并定义某种“重置”函数。

    class MyIterator(object):
        def __init__(self):
            self.reset()
    
        def reset(self):
            self.i = 5
    
        def __iter__(self):
            return self
    
        def next(self):
            i = self.i
            if i > 0:
                self.i -= 1
                return i
            else:
                raise StopIteration()
    
    my_iterator = MyIterator()
    
    for x in my_iterator:
        print x
    
    print 'resetting...'
    my_iterator.reset()
    
    for x in my_iterator:
        print x
    

    https://docs.python.org/2/library/stdtypes.html#iterator-types http://anandology.com/python-practice-book/iterators.html

        14
  •  0
  •   axolotl    6 年前

    使用包装函数处理 StopIteration

    可以向生成器生成函数编写一个简单的包装函数,该函数在生成器耗尽时跟踪。它将使用 停止迭代 当生成器到达迭代结束时引发异常。

    import types
    
    def generator_wrapper(function=None, **kwargs):
        assert function is not None, "Please supply a function"
        def inner_func(function=function, **kwargs):
            generator = function(**kwargs)
            assert isinstance(generator, types.GeneratorType), "Invalid function"
            try:
                yield next(generator)
            except StopIteration:
                generator = function(**kwargs)
                yield next(generator)
        return inner_func
    

    正如您可以在上面看到的,当我们的包装函数捕获 停止迭代 异常,它只是重新初始化生成器对象(使用函数调用的另一个实例)。

    然后,假设您在下面的某个地方定义了提供函数的生成器,您可以使用python函数decorator语法将其隐式包装:

    @generator_wrapper
    def generator_generating_function(**kwargs):
        for item in ["a value", "another value"]
            yield item
    
        15
  •  -3
  •   OlegOS    11 年前

    它可以由代码对象完成。下面是例子。

    code_str="y=(a for a in [1,2,3,4])"
    code1=compile(code_str,'<string>','single')
    exec(code1)
    for i in y: print i
    

    一 二 三 4

    for i in y: print i
    
    
    exec(code1)
    for i in y: print i
    

    一 二 三 四