可以非延迟地调用Python生成器吗?

时间:2018-11-16 07:45:36

标签: python generator

我知道在Python中,生成器是延迟调用的。例如:

>>> def G():
...     print('this was evaluated now 1')
...     yield 1
...     print('this was evaluated now 2')
...     yield 2
...
>>> g = G()
>>> next(g)
this was evaluated now 1
1
>>> next(g)
this was evaluated now 2
2

仅在调用第一个print('this was evaluated now 1')之后才评估行next(g)

我想知道是否有一种简单的方法可以懒惰地调用生成器。这意味着,在调用g = G()时,该函数将计算直至并包括第一个yield结果的所有内容,而不会实际产生。然后,在第一次调用next(g)时,将产生已经计算的结果,并且还将计算直到第二个yield结果的所有内容。依此类推。

如何实现?


这是此非懒惰方案下的预期行为:

>>> g = G()
this was evaluated now 1
>>> next(g)
1
this was evaluated now 2
>>> next(g)
2

这是解决方案尝试,不起作用:

>>> class NonLazyGenerator():
...     def __init__(self,G):
...         self.g = G()
...         self.next_value = next(self.g)
...
...     def __next__(self):
...         current_value = self.next_value
...         try:
...             self.next_value = next(self.g)
...         except StopIteration:
...             pass
...         return current_value
...
>>> g = NonLazyGenerator(G)
this was evaluated now 1
>>> next(g)
this was evaluated now 2
1
>>> next(g)
2

这失败了,因为仅在return语句之后才产生值,而直到下一个yield的所有内容的计算都在return语句之前进行。这个示例使我意识到,可能无法执行我要寻找的内容,因为它需要在函数返回后执行一些步骤(可能需要多线程处理)。

2 个答案:

答案 0 :(得分:1)

您可能会为此编写某种装饰器,例如:

def eagergenerator(mygen):
    class GeneratorWrapper:
        def __init__(self, *args, **kwargs):
            self.g = mygen(*args, **kwargs)
            self.last = next(self.g)
        def __iter__(self):
            return self
        def __next__(self):
            if self.last is self:
                raise StopIteration
            fake_yield = self.last
            try:
                self.last = next(self.g)
                return fake_yield
            except StopIteration:
                self.last = self
                return fake_yield
    return GeneratorWrapper

然后,您可以简单地装饰普通的发电机:

@eagergenerator
def G():
    print("one")
    yield 1
    print("two")
    yield 2

其工作方式如下:

>>> g = G()                               
one                                       
>>> next(g)                               
two                                       
1                                         
>>> next(g)                               
2                                         
>>> next(g)                               
Traceback (most recent call last):        
  File "<stdin>", line 1, in <module>     
  File "eagergen.py", line 10, in __next__
    raise StopIteration                   
StopIteration                             
>>>                                       

答案 1 :(得分:1)

信用:这是受@ L3viathan答案的启发

在此版本中,itertools.tee用于存储包装器在原始生成器后面的一个屈服值。

import itertools

def eagergenerator(mygen):
    class GeneratorWrapper:
        def __init__(self, *args, **kwargs):
            self.g0, self.g1 = itertools.tee(mygen(*args, **kwargs))
            self._next0()
        def _next0(self):
            try:
                next(self.g0)
            except StopIteration:
                pass
        def __iter__(self):
            return self
        def __next__(self):
            self._next0()
            return next(self.g1)
    return GeneratorWrapper