Могут ли генераторы Python вызываться не лениво? - PullRequest
0 голосов
/ 16 ноября 2018

Я знаю, что в 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 голосов
/ 16 ноября 2018

кредит: это было вдохновлено @ 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
0 голосов
/ 16 ноября 2018

Вы могли бы написать какой-нибудь декоратор для него, например:

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                             
>>>                                       
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...