Resetowanie obiektu generatora w Pythonie

Mam obiekt generatora zwrócony przez wiele wydajności. Przygotowanie do wywołania tego generatora jest dość czasochłonną pracą. Dlatego chcę ponownie użyć generator kilka razy.

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

Oczywiście, mam na myśli kopiowanie treści do prostej listy.

Author: Cœur, 2009-08-13

15 answers

Inną opcją jest użycie itertools.tee() funkcja tworzenia drugiej wersji generatora:

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

Może to być korzystne z punktu widzenia wykorzystania pamięci, jeśli oryginalna iteracja nie przetworzy wszystkich elementów.

 85
Author: Ants Aasma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-20 16:49:28

Generatory nie mogą być odwrócone. Masz następujące opcje:

  1. Uruchom ponownie funkcję generatora, restartując generowanie:

    y = FunctionWithYield()
    for x in y: print(x)
    y = FunctionWithYield()
    for x in y: print(x)
    
  2. Przechowuj wyniki generatora w strukturze danych na pamięci lub dysku, którą możesz powtórzyć:

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

Minusem opcji 1 jest to, że oblicza wartości ponownie. Jeśli to obciąża procesor, kończysz obliczanie dwa razy. Z drugiej strony minusem 2 jest magazyn. Cała lista wartości zostanie zapisana w pamięci. Jeśli jest zbyt wiele wartości, może to być niepraktyczne.

Więc masz klasyczną pamięć a procesor . Nie wyobrażam sobie sposobu przewijania generatora bez zapisywania wartości lub obliczania ich ponownie.

 111
Author: nosklo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-13 11:28:45
>>> 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
 26
Author: aaab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-04 16:24:01

Prawdopodobnie najprostszym rozwiązaniem jest owinięcie kosztownej części w obiekt i przekazanie jej do generatora:

data = ExpensiveSetup()
for x in FunctionWithYield(data): pass
for x in FunctionWithYield(data): pass
W ten sposób możesz buforować kosztowne obliczenia.

Jeśli możesz zachować wszystkie wyniki w pamięci RAM w tym samym czasie, użyj list(), aby zmaterializować wyniki generatora na zwykłej liście i pracować z tym.

 22
Author: Aaron Digulla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-13 11:39:50

Chcę zaproponować inne rozwiązanie starego problemu

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)

Korzyść z tego w porównaniu do czegoś podobnego list(iterator) jest taka, że jest to O(1) złożoność przestrzeni i list(iterator) jest O(n). Wadą jest to, że jeśli masz dostęp tylko do iteratora, ale nie do funkcji, która go wyprodukowała, nie możesz użyć tej metody. Na przykład, może się wydawać rozsądne, aby wykonać następujące czynności, ale to nie zadziała.

g = (x * x for x in range(5))

squares = IterableAdapter(lambda: g)

for x in squares: print(x)
for x in squares: print(x)
 12
Author: michaelsnowden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-14 21:24:16

Jeśli odpowiedź Grzegorzoledzkiego nie wystarczy, prawdopodobnie możesz użyć send(), aby osiągnąć swój cel. Zobacz PEP-0342 aby uzyskać więcej szczegółów na temat ulepszonych generatorów i wyrażeń wydajności.

Aktualizacja: Zobacz też itertools.tee(). Wiąże się to z pewną ilością tej pamięci w porównaniu z obróbką wymienioną powyżej, ale to Może zaoszczędzić trochę pamięci, ponieważ samo przechowywanie generatora skutkuje list; zależy to od tego, jak używasz generatora.

 4
Author: Hank Gay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-13 11:54:30

Jeśli twój generator jest czysty w tym sensie, że jego wynik zależy tylko od podanych argumentów i numeru kroku, i chcesz, aby generator wynikowy był restartowalny, oto przykładowy fragment, który może być przydatny:

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

Wyjścia:

[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
 3
Author: Ben Usman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-01 18:43:06

Możesz zdefiniować funkcję, która zwraca twój generator

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

  return FunctionWithYield

Teraz możesz robić tyle razy, ile chcesz:

for x in f()(generator_args): print(x)
for x in f()(generator_args): print(x)
 2
Author: SMeznaric,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-07 09:55:55

Z oficjalnej dokumentacji tee :

Ogólnie, jeśli jeden iterator wykorzystuje większość lub wszystkie dane przed uruchamia się kolejny iterator, szybsze jest użycie list () zamiast tee ().

Więc najlepiej użyć list(iterable) zamiast w Twoim przypadku.

 1
Author: Shubham Chaudhary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-08 13:59:22

Nie ma opcji resetowania iteratorów. Iterator Zwykle wyskakuje, gdy iteracja odbywa się za pomocą funkcji next(). Jedynym sposobem jest zrobienie kopii zapasowej przed iteracją na obiekcie iterator. Sprawdź poniżej.

Tworzenie obiektu iteratora z pozycjami od 0 do 9

i=iter(range(10))

Iteracja przez funkcję next (), która wyskoczy

print(next(i))

Konwersja obiektu iterator do list

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

Więc pozycja 0 już wyskoczyła. Również wszystkie elementy są popped jak my przekonwertował iterator do list.

next(L) 

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

Więc musisz przekonwertować iterator na listy do tworzenia kopii zapasowych przed rozpoczęciem iteracji. Lista może być przekonwertowana do iteratora za pomocą iter(<list-object>)

 1
Author: Amalraj Victory,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-04 18:30:31

Możesz teraz użyć more_itertools.seekable (Narzędzie innej firmy), które umożliwia Resetowanie iteratorów.

Install via > 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)

Uwaga: zużycie pamięci rośnie wraz z rozwojem iteratora, więc uważaj na duże iteraby.

 1
Author: pylang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-06 02:23:14

Nie jestem pewien, co miałeś na myśli przez drogie preparaty, ale myślę, że rzeczywiście masz

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)
Jeśli tak jest, dlaczego nie użyć ponownie data?
 0
Author: ilya n.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-13 12:38:04

Ok, mówisz, że chcesz zadzwonić do generatora wiele razy, ale inicjalizacja jest kosztowna... A co z czymś takim?

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

Alternatywnie, możesz po prostu stworzyć własną klasę, która podąża za protokołem iteratora i definiuje jakąś funkcję 'reset'.

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

 0
Author: tvt173,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-22 01:02:16

Używanie funkcji wrapper do obsługi StopIteration

Możesz napisać prostą funkcję wrappera do generatora-generującą funkcję, która śledzi, gdy generator jest wyczerpany. Zrobi to za pomocą wyjątku StopIteration, który generator rzuca, gdy osiągnie koniec iteracji.

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

Jak widać powyżej, gdy nasza funkcja wrapper wychwytuje wyjątek StopIteration, po prostu ponownie inicjalizuje obiekt generatora(używając innej instancji wywołania funkcji).

I wtedy, zakładając, że zdefiniuj gdzieś swoją funkcję dostarczającą generator, jak poniżej, możesz użyć składni dekoratora funkcji Pythona, aby zawinąć ją niejawnie:

@generator_wrapper
def generator_generating_function(**kwargs):
    for item in ["a value", "another value"]
        yield item
 0
Author: Aalok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-26 18:56:22

Można to zrobić za pomocą obiektu code. Oto przykład.

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

 -3
Author: OlegOS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-27 01:56:15