复位发生器对象在Python(Resetting generator object in Pytho

2019-06-18 10:56发布

我有发电机对象由多个产量返回。 准备把这种发电机是相当耗时的操作。 这就是为什么我要重用发电机几次。

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

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

Answer 1:

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

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

这可能是从查看内存使用点有益的,如果原来的迭代可能无法处理所有的项目。



Answer 2:

发电机不能倒带。 您有以下选择:

  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密集型你最终计算两次。 在另一方面,2的缺点是存储。 值的整个列表将被存储在内存中。 如果有太多的值,可以是不现实的。

所以,你有经典的记忆与加工权衡 。 我无法想象复卷发电机没有任何存储的值或者再次计算它们的方式。



Answer 3:

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


Answer 4:

可能是最简单的解决方案是包裹昂贵的部分中的对象,并传递到发电机:

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

这样一来,你可以缓存昂贵计算。

如果你能保持在RAM中的所有结果在同一时间,然后使用list()兑现发电机的结果在一个普通的列表,并与工作。



Answer 5:

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

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)空间复杂度和list(iterator)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)


Answer 6:

如果GrzegorzOledzki的回答是不够的,你很可能使用send()来实现自己的目标。 见PEP-0342对增强发电机和产量表现更多的细节。

更新:另见itertools.tee() 它涉及一些记忆与上述处理权衡的,但它可能会节省一些内存超过只是存储在一个发电机结果list ; 这取决于你如何使用发电机。



Answer 7:

从发球台的官方文档 :

一般来说,如果一个迭代器使用了大部分或全部数据的另一迭代开始前,它是更快地使用的三通()而不是列表()。

所以最好使用list(iterable)中,而不是你的情况。



Answer 8:

如果您的发电机在一定意义上,它的输出只依赖于传递的参数和步数纯,你要得到的发电机可以重启,这里有可能是得心应手排序片段:

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


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


Answer 10:

没有选项重置迭代器。 迭代器通常弹出时,它通过迭代next()函数。 唯一的办法就是iterator对象上迭代前进行备份。 检查下面。

与项0至9创建迭代器对象

i=iter(range(10))

通过将弹出next()函数迭代

print(next(i))

转换iterator对象列出

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

如此项0已经弹出。 此外所有的项目被弹出,因为我们转换的迭代器上市。

next(L) 

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

因此,你需要开始迭代之前的迭代器转换成列表进行备份。 列表可以转换到与迭代器iter(<list-object>)



Answer 11:

您现在可以使用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的。



Answer 12:

使用包装函数来处理StopIteration

你可以写一个简单的包装功能,以您的发电机生成功能当发电机耗尽跟踪。 它会做这样使用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

正如你可以在上面发现,当我们的包装函数接球StopIteration例外,它只是重新初始化生成器对象(使用函数调用的另一个实例)。

然后,假设你的地方定义为下方的发电机供电的功能,你可以使用Python函数修饰语法隐含地把它包:

@generator_wrapper
def generator_generating_function(**kwargs):
    for item in ["a value", "another value"]
        yield item


Answer 13:

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

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



Answer 14:

好吧,你说你想要一个发电机多次打电话,但初始化是昂贵的...怎么样这样的事情?

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



Answer 15:

它可以通过代码对象来完成。 这里是例子。

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

1 2 3 4

for i in y: print i


exec(code1)
for i in y: print i

1 2 3 4



文章来源: Resetting generator object in Python