Mam obiekt generatora zwrócony przez wielokrotną wydajność. Przygotowanie do wywołania tego generatora jest raczej czasochłonną operacją. Dlatego chcę kilka razy ponownie użyć generatora.
y = FunctionWithYield()
for x in y: print(x)
#here must be something to reset 'y'
for x in y: print(x)
Oczywiście myślę o skopiowaniu treści na prostą listę. Czy jest sposób na zresetowanie mojego generatora?
y = list(y)
z pozostałą częścią swojego kodu bez zmian.Generatorów nie można przewijać. Masz następujące możliwości:
Uruchom ponownie funkcję generatora, ponownie uruchamiając generowanie:
Przechowywanie generatora powoduje powstanie struktury danych w pamięci lub na dysku, którą można powtórzyć:
Wadą opcji 1 jest ponowne obliczenie wartości. Jeśli wymaga to dużej mocy obliczeniowej, w końcu obliczasz dwa razy. Z drugiej strony wadą 2 jest przechowywanie. Cała lista wartości zostanie zapisana w pamięci. Jeśli wartości jest zbyt wiele, może to być niepraktyczne.
Masz więc klasyczny kompromis między pamięcią a przetwarzaniem . Nie wyobrażam sobie sposobu na przewinięcie generatora bez przechowywania wartości lub ich ponownego obliczania.
źródło
źródło
Prawdopodobnie najprostszym rozwiązaniem jest owinięcie drogiej części w obiekt i przekazanie go do generatora:
W ten sposób możesz buforować kosztowne obliczenia.
Jeśli możesz przechowywać wszystkie wyniki w pamięci RAM w tym samym czasie, użyj,
list()
aby zmaterializować wyniki generatora na zwykłej liście i pracuj z tym.źródło
Chcę zaproponować inne rozwiązanie starego problemu
Zaletą tego w porównaniu z czymś podobnym
list(iterator)
jest to, że jest toO(1)
złożoność przestrzeni ilist(iterator)
jestO(n)
. Wadą jest to, że jeśli masz dostęp tylko do iteratora, ale nie do funkcji, która utworzyła iterator, nie możesz użyć tej metody. Na przykład wykonanie poniższych czynności może wydawać się rozsądne, ale nie zadziała.źródło
Jeśli odpowiedź Grzegorza Oledzkiego nie wystarczy, prawdopodobnie przydałbyś się
send()
do osiągnięcia celu. Zobacz PEP-0342 więcej szczegółów na temat rozszerzonych generatorów i wyrażeń dochodowości.UPDATE: Zobacz także
itertools.tee()
. Obejmuje to część wspomnianego powyżej kompromisu między pamięcią a przetwarzaniem, ale może zaoszczędzić trochę pamięci w porównaniu z samym przechowywaniem wyników generatora wlist
; zależy to od tego, jak używasz generatora.źródło
Jeśli twój generator jest czysty w tym sensie, że jego wyjście zależy tylko od przekazanych argumentów i numeru kroku, a chcesz, aby wynikowy generator był uruchamiany ponownie, oto fragment kodu sortowania, który może być przydatny:
wyjścia:
źródło
Z oficjalnej dokumentacji koszulki :
Dlatego najlepiej jest użyć
list(iterable)
zamiast tego w twoim przypadku.źródło
list()
tee()
jeśli jeden iterator zużyje wszystkie wartości - tak totee
działa.Używanie funkcji opakowującej do obsługi
StopIteration
Możesz napisać prostą funkcję opakowującą do funkcji generującej generator, która śledzi, kiedy generator jest wyczerpany. Zrobi to przy użyciu
StopIteration
wyjątku generowanego przez generator, gdy osiągnie koniec iteracji.Jak widać powyżej, kiedy nasza funkcja opakowująca przechwytuje plik
StopIteration
wyjątek, po prostu ponownie inicjalizuje obiekt generatora (używając innej instancji wywołania funkcji).A potem, zakładając, że zdefiniujesz swoją funkcję dostarczającą generator gdzieś, jak poniżej, możesz użyć składni dekoratora funkcji Pythona, aby ją zawinąć niejawnie:
źródło
Możesz zdefiniować funkcję, która zwraca generator
Teraz możesz po prostu zrobić tyle razy, ile chcesz:
źródło
Nie jestem pewien, co miałeś na myśli mówiąc o drogim przygotowaniu, ale myślę, że tak naprawdę masz
Jeśli tak jest, dlaczego nie wykorzystać ponownie
data
?źródło
Nie ma opcji resetowania iteratorów. Iterator zwykle wyskakuje podczas iteracji
next()
funkcję. Jedynym sposobem jest wykonanie kopii zapasowej przed iteracją na obiekcie iteratora. Sprawdź poniżej.Tworzenie obiektu iteratora z elementami od 0 do 9
Iterowanie po funkcji next (), która wyskoczy
Konwersja obiektu iteratora na listę
więc element 0 już wyskoczył. Również wszystkie elementy są pobierane, gdy konwertowaliśmy iterator na listę.
Musisz więc przekonwertować iterator na listy do tworzenia kopii zapasowych przed rozpoczęciem iteracji. Listę można przekonwertować na iterator z
iter(<list-object>)
źródło
Możesz teraz użyć
more_itertools.seekable
(narzędzia innej firmy), które umożliwia resetowanie iteratorów.Zainstaluj przez
> pip install more_itertools
Uwaga: zużycie pamięci rośnie wraz z rozwojem iteratora, więc uważaj na duże iteracje.
źródło
Możesz to zrobić, używając itertools.cycle () , możesz utworzyć iterator za pomocą tej metody, a następnie wykonać pętlę for na iteratorze, który zapętli jego wartości.
Na przykład:
wygeneruje 20 liczb, od 0 do 4 wielokrotnie.
Notatka z dokumentów:
źródło
Ok, mówisz, że chcesz wielokrotnie wywoływać generator, ale inicjalizacja jest droga ... A co z czymś takim?
Alternatywnie, możesz po prostu stworzyć własną klasę, która będzie zgodna z protokołem iteratora i definiuje jakąś funkcję „resetowania”.
https://docs.python.org/2/library/stdtypes.html#iterator-types http://anandology.com/python-practice-book/iterators.html
źródło
__call__
Moja odpowiedź rozwiązuje nieco inny problem: jeśli inicjalizacja generatora jest kosztowna, a wygenerowanie każdego wygenerowanego obiektu jest drogie. Ale musimy wielokrotnie zużywać generator w wielu funkcjach. Aby wywołać generator i każdy wygenerowany obiekt dokładnie raz, możemy użyć wątków i uruchomić każdą zużywającą się metodę w innym wątku. Możemy nie osiągnąć prawdziwego paralelizmu dzięki GIL, ale osiągniemy nasz cel.
Takie podejście sprawdziło się w następującym przypadku: model głębokiego uczenia przetwarza wiele obrazów. Rezultatem jest wiele masek dla wielu obiektów na obrazie. Każda maska zajmuje pamięć. Mamy około 10 metod, które tworzą różne statystyki i metryki, ale pobierają wszystkie obrazy naraz. Wszystkie obrazy nie mieszczą się w pamięci. Metody można łatwo przepisać, aby akceptowały iterator.
Zastosowanie:
źródło
itertools.islice
lub asynchronicznieaiostream.stream.take
, a ten post pozwala ci to zrobić w sposób asyn / await stackoverflow.com/a/42379188/149818Można to zrobić za pomocą obiektu kodu. Oto przykład.
1 2 3 4
1 2 3 4
źródło
exec
tego nieco niezalecane w tak prostym przypadku.